mirror of
https://github.com/Microsoft/vscode
synced 2024-10-04 10:27:46 +00:00
Merge branch 'main' into joh/cellUri
This commit is contained in:
commit
44e25ba96e
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 }}
|
||||
|
|
|
@ -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
|
||||
|
@ -31,9 +22,11 @@ variables:
|
|||
|
||||
stages:
|
||||
- stage: Compile
|
||||
displayName: Compile & Hygiene
|
||||
jobs:
|
||||
- job: Compile
|
||||
pool: vscode-1es-vscode-linux-18.04
|
||||
displayName: Compile & Hygiene
|
||||
pool: vscode-1es-vscode-linux-20.04
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
steps:
|
||||
|
@ -41,74 +34,14 @@ stages:
|
|||
parameters:
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
|
||||
- stage: LinuxServerDependencies
|
||||
- stage: Test
|
||||
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 }}
|
||||
|
||||
- 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: Linux
|
||||
dependsOn:
|
||||
- Compile
|
||||
- LinuxServerDependencies
|
||||
pool: vscode-1es-vscode-linux-18.04
|
||||
jobs:
|
||||
- job: Linuxx64UnitTest
|
||||
displayName: Unit Tests
|
||||
container: vscode-bionic-x64
|
||||
displayName: Linux (Unit Tests)
|
||||
pool: vscode-1es-vscode-linux-20.04
|
||||
# container: vscode-bionic-x64
|
||||
timeoutInMinutes: 60
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
NPM_ARCH: x64
|
||||
|
@ -122,8 +55,10 @@ stages:
|
|||
VSCODE_RUN_INTEGRATION_TESTS: false
|
||||
VSCODE_RUN_SMOKE_TESTS: false
|
||||
- job: Linuxx64IntegrationTest
|
||||
displayName: Integration Tests
|
||||
container: vscode-bionic-x64
|
||||
displayName: Linux (Integration Tests)
|
||||
pool: vscode-1es-vscode-linux-20.04
|
||||
# container: vscode-bionic-x64
|
||||
timeoutInMinutes: 60
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
NPM_ARCH: x64
|
||||
|
@ -137,8 +72,10 @@ stages:
|
|||
VSCODE_RUN_INTEGRATION_TESTS: true
|
||||
VSCODE_RUN_SMOKE_TESTS: false
|
||||
- job: Linuxx64SmokeTest
|
||||
displayName: Smoke Tests
|
||||
container: vscode-bionic-x64
|
||||
displayName: Linux (Smoke Tests)
|
||||
pool: vscode-1es-vscode-linux-20.04
|
||||
# container: vscode-bionic-x64
|
||||
timeoutInMinutes: 60
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
NPM_ARCH: x64
|
||||
|
@ -152,50 +89,94 @@ stages:
|
|||
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: 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
|
||||
|
||||
# - 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
|
||||
|
|
|
@ -108,9 +108,11 @@ variables:
|
|||
- name: VSCODE_BUILD_STAGE_WINDOWS
|
||||
value: ${{ or(eq(parameters.VSCODE_BUILD_WIN32, true), eq(parameters.VSCODE_BUILD_WIN32_32BIT, true), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}
|
||||
- name: VSCODE_BUILD_STAGE_LINUX
|
||||
value: ${{ or(eq(parameters.VSCODE_BUILD_LINUX, true), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true), eq(parameters.VSCODE_BUILD_LINUX_ALPINE, true), eq(parameters.VSCODE_BUILD_LINUX_ALPINE_ARM64, true), eq(parameters.VSCODE_BUILD_WEB, true)) }}
|
||||
value: ${{ or(eq(parameters.VSCODE_BUILD_LINUX, true), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true), eq(parameters.VSCODE_BUILD_LINUX_ALPINE, true), eq(parameters.VSCODE_BUILD_LINUX_ALPINE_ARM64, true)) }}
|
||||
- name: VSCODE_BUILD_STAGE_MACOS
|
||||
value: ${{ or(eq(parameters.VSCODE_BUILD_MACOS, true), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true)) }}
|
||||
- name: VSCODE_BUILD_STAGE_WEB
|
||||
value: ${{ eq(parameters.VSCODE_BUILD_WEB, true) }}
|
||||
- name: VSCODE_CIBUILD
|
||||
value: ${{ in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI') }}
|
||||
- name: VSCODE_PUBLISH
|
||||
|
@ -176,45 +178,45 @@ stages:
|
|||
pool: vscode-1es-windows
|
||||
jobs:
|
||||
- ${{ if eq(variables['VSCODE_CIBUILD'], true) }}:
|
||||
- job: WindowsUnitTests
|
||||
displayName: Unit Tests
|
||||
timeoutInMinutes: 60
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
steps:
|
||||
- template: win32/product-build-win32.yml
|
||||
parameters:
|
||||
VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }}
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_RUN_UNIT_TESTS: true
|
||||
VSCODE_RUN_INTEGRATION_TESTS: false
|
||||
VSCODE_RUN_SMOKE_TESTS: false
|
||||
- job: WindowsIntegrationTests
|
||||
displayName: Integration Tests
|
||||
timeoutInMinutes: 60
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
steps:
|
||||
- template: win32/product-build-win32.yml
|
||||
parameters:
|
||||
VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }}
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_RUN_UNIT_TESTS: false
|
||||
VSCODE_RUN_INTEGRATION_TESTS: true
|
||||
VSCODE_RUN_SMOKE_TESTS: false
|
||||
- job: WindowsSmokeTests
|
||||
displayName: Smoke Tests
|
||||
timeoutInMinutes: 60
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
steps:
|
||||
- template: win32/product-build-win32.yml
|
||||
parameters:
|
||||
VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }}
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_RUN_UNIT_TESTS: false
|
||||
VSCODE_RUN_INTEGRATION_TESTS: false
|
||||
VSCODE_RUN_SMOKE_TESTS: true
|
||||
- job: WindowsUnitTests
|
||||
displayName: Unit Tests
|
||||
timeoutInMinutes: 60
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
steps:
|
||||
- template: win32/product-build-win32.yml
|
||||
parameters:
|
||||
VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }}
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_RUN_UNIT_TESTS: true
|
||||
VSCODE_RUN_INTEGRATION_TESTS: false
|
||||
VSCODE_RUN_SMOKE_TESTS: false
|
||||
- job: WindowsIntegrationTests
|
||||
displayName: Integration Tests
|
||||
timeoutInMinutes: 60
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
steps:
|
||||
- template: win32/product-build-win32.yml
|
||||
parameters:
|
||||
VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }}
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_RUN_UNIT_TESTS: false
|
||||
VSCODE_RUN_INTEGRATION_TESTS: true
|
||||
VSCODE_RUN_SMOKE_TESTS: false
|
||||
- job: WindowsSmokeTests
|
||||
displayName: Smoke Tests
|
||||
timeoutInMinutes: 60
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
steps:
|
||||
- template: win32/product-build-win32.yml
|
||||
parameters:
|
||||
VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }}
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_RUN_UNIT_TESTS: false
|
||||
VSCODE_RUN_INTEGRATION_TESTS: false
|
||||
VSCODE_RUN_SMOKE_TESTS: true
|
||||
|
||||
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32, true)) }}:
|
||||
- job: Windows
|
||||
|
@ -291,51 +293,51 @@ stages:
|
|||
pool: vscode-1es-linux
|
||||
jobs:
|
||||
- ${{ if eq(variables['VSCODE_CIBUILD'], true) }}:
|
||||
- job: Linuxx64UnitTest
|
||||
displayName: Unit Tests
|
||||
container: vscode-bionic-x64
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
NPM_ARCH: x64
|
||||
DISPLAY: ":10"
|
||||
steps:
|
||||
- template: linux/product-build-linux-client.yml
|
||||
parameters:
|
||||
VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }}
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_RUN_UNIT_TESTS: true
|
||||
VSCODE_RUN_INTEGRATION_TESTS: false
|
||||
VSCODE_RUN_SMOKE_TESTS: false
|
||||
- job: Linuxx64IntegrationTest
|
||||
displayName: Integration Tests
|
||||
container: vscode-bionic-x64
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
NPM_ARCH: x64
|
||||
DISPLAY: ":10"
|
||||
steps:
|
||||
- template: linux/product-build-linux-client.yml
|
||||
parameters:
|
||||
VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }}
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_RUN_UNIT_TESTS: false
|
||||
VSCODE_RUN_INTEGRATION_TESTS: true
|
||||
VSCODE_RUN_SMOKE_TESTS: false
|
||||
- job: Linuxx64SmokeTest
|
||||
displayName: Smoke Tests
|
||||
container: vscode-bionic-x64
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
NPM_ARCH: x64
|
||||
DISPLAY: ":10"
|
||||
steps:
|
||||
- template: linux/product-build-linux-client.yml
|
||||
parameters:
|
||||
VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }}
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_RUN_UNIT_TESTS: false
|
||||
VSCODE_RUN_INTEGRATION_TESTS: false
|
||||
VSCODE_RUN_SMOKE_TESTS: true
|
||||
- job: Linuxx64UnitTest
|
||||
displayName: Unit Tests
|
||||
container: vscode-bionic-x64
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
NPM_ARCH: x64
|
||||
DISPLAY: ":10"
|
||||
steps:
|
||||
- template: linux/product-build-linux-client.yml
|
||||
parameters:
|
||||
VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }}
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_RUN_UNIT_TESTS: true
|
||||
VSCODE_RUN_INTEGRATION_TESTS: false
|
||||
VSCODE_RUN_SMOKE_TESTS: false
|
||||
- job: Linuxx64IntegrationTest
|
||||
displayName: Integration Tests
|
||||
container: vscode-bionic-x64
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
NPM_ARCH: x64
|
||||
DISPLAY: ":10"
|
||||
steps:
|
||||
- template: linux/product-build-linux-client.yml
|
||||
parameters:
|
||||
VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }}
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_RUN_UNIT_TESTS: false
|
||||
VSCODE_RUN_INTEGRATION_TESTS: true
|
||||
VSCODE_RUN_SMOKE_TESTS: false
|
||||
- job: Linuxx64SmokeTest
|
||||
displayName: Smoke Tests
|
||||
container: vscode-bionic-x64
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
NPM_ARCH: x64
|
||||
DISPLAY: ":10"
|
||||
steps:
|
||||
- template: linux/product-build-linux-client.yml
|
||||
parameters:
|
||||
VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }}
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_RUN_UNIT_TESTS: false
|
||||
VSCODE_RUN_INTEGRATION_TESTS: false
|
||||
VSCODE_RUN_SMOKE_TESTS: true
|
||||
|
||||
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX, true)) }}:
|
||||
- job: Linuxx64
|
||||
|
@ -430,13 +432,6 @@ stages:
|
|||
steps:
|
||||
- template: linux/product-build-alpine.yml
|
||||
|
||||
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WEB, true)) }}:
|
||||
- job: LinuxWeb
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
steps:
|
||||
- template: web/product-build-web.yml
|
||||
|
||||
- ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_MACOS'], true)) }}:
|
||||
- stage: macOS
|
||||
dependsOn:
|
||||
|
@ -447,62 +442,8 @@ stages:
|
|||
BUILDSECMON_OPT_IN: true
|
||||
jobs:
|
||||
- ${{ if eq(variables['VSCODE_CIBUILD'], true) }}:
|
||||
- job: macOSUnitTest
|
||||
displayName: Unit Tests
|
||||
timeoutInMinutes: 90
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
steps:
|
||||
- template: darwin/product-build-darwin.yml
|
||||
parameters:
|
||||
VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }}
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_RUN_UNIT_TESTS: true
|
||||
VSCODE_RUN_INTEGRATION_TESTS: false
|
||||
VSCODE_RUN_SMOKE_TESTS: false
|
||||
- job: macOSIntegrationTest
|
||||
displayName: Integration Tests
|
||||
timeoutInMinutes: 90
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
steps:
|
||||
- template: darwin/product-build-darwin.yml
|
||||
parameters:
|
||||
VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }}
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_RUN_UNIT_TESTS: false
|
||||
VSCODE_RUN_INTEGRATION_TESTS: true
|
||||
VSCODE_RUN_SMOKE_TESTS: false
|
||||
- job: macOSSmokeTest
|
||||
displayName: Smoke Tests
|
||||
timeoutInMinutes: 90
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
steps:
|
||||
- template: darwin/product-build-darwin.yml
|
||||
parameters:
|
||||
VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }}
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_RUN_UNIT_TESTS: false
|
||||
VSCODE_RUN_INTEGRATION_TESTS: false
|
||||
VSCODE_RUN_SMOKE_TESTS: true
|
||||
|
||||
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS, true)) }}:
|
||||
- job: macOS
|
||||
timeoutInMinutes: 90
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
steps:
|
||||
- template: darwin/product-build-darwin.yml
|
||||
parameters:
|
||||
VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }}
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_RUN_UNIT_TESTS: false
|
||||
VSCODE_RUN_INTEGRATION_TESTS: false
|
||||
VSCODE_RUN_SMOKE_TESTS: false
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_STEP_ON_IT, false) }}:
|
||||
- job: macOSTest
|
||||
- job: macOSUnitTest
|
||||
displayName: Unit Tests
|
||||
timeoutInMinutes: 90
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
|
@ -511,19 +452,73 @@ stages:
|
|||
parameters:
|
||||
VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }}
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_RUN_UNIT_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }}
|
||||
VSCODE_RUN_INTEGRATION_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }}
|
||||
VSCODE_RUN_SMOKE_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }}
|
||||
|
||||
- ${{ if eq(variables['VSCODE_PUBLISH'], true) }}:
|
||||
- job: macOSSign
|
||||
dependsOn:
|
||||
- macOS
|
||||
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-sign.yml
|
||||
- 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
|
||||
|
||||
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS, true)) }}:
|
||||
- job: macOS
|
||||
timeoutInMinutes: 90
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
steps:
|
||||
- template: darwin/product-build-darwin.yml
|
||||
parameters:
|
||||
VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }}
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_RUN_UNIT_TESTS: false
|
||||
VSCODE_RUN_INTEGRATION_TESTS: false
|
||||
VSCODE_RUN_SMOKE_TESTS: false
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_STEP_ON_IT, false) }}:
|
||||
- job: macOSTest
|
||||
timeoutInMinutes: 90
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
steps:
|
||||
- template: darwin/product-build-darwin.yml
|
||||
parameters:
|
||||
VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }}
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_RUN_UNIT_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }}
|
||||
VSCODE_RUN_INTEGRATION_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }}
|
||||
VSCODE_RUN_SMOKE_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }}
|
||||
|
||||
- ${{ if eq(variables['VSCODE_PUBLISH'], true) }}:
|
||||
- job: macOSSign
|
||||
dependsOn:
|
||||
- macOS
|
||||
timeoutInMinutes: 90
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
steps:
|
||||
- template: darwin/product-build-darwin-sign.yml
|
||||
|
||||
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true)) }}:
|
||||
- job: macOSARM64
|
||||
|
@ -570,6 +565,19 @@ stages:
|
|||
steps:
|
||||
- template: darwin/product-build-darwin-sign.yml
|
||||
|
||||
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_WEB'], true)) }}:
|
||||
- stage: Web
|
||||
dependsOn:
|
||||
- Compile
|
||||
pool: vscode-1es-linux
|
||||
jobs:
|
||||
- ${{ if eq(parameters.VSCODE_BUILD_WEB, true) }}:
|
||||
- job: Web
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
steps:
|
||||
- template: web/product-build-web.yml
|
||||
|
||||
- ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), ne(variables['VSCODE_PUBLISH'], 'false')) }}:
|
||||
- stage: Publish
|
||||
dependsOn:
|
||||
|
|
|
@ -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: |
|
||||
|
|
|
@ -46,6 +46,7 @@ $stages = @(
|
|||
if ($env:VSCODE_BUILD_STAGE_WINDOWS -eq 'True') { 'Windows' }
|
||||
if ($env:VSCODE_BUILD_STAGE_LINUX -eq 'True') { 'Linux' }
|
||||
if ($env:VSCODE_BUILD_STAGE_MACOS -eq 'True') { 'macOS' }
|
||||
if ($env:VSCODE_BUILD_STAGE_WEB -eq 'True') { 'Web' }
|
||||
)
|
||||
|
||||
do {
|
||||
|
|
|
@ -109,6 +109,7 @@ steps:
|
|||
if ($env:VSCODE_BUILD_STAGE_WINDOWS -eq 'True') { 'Windows' }
|
||||
if ($env:VSCODE_BUILD_STAGE_LINUX -eq 'True') { 'Linux' }
|
||||
if ($env:VSCODE_BUILD_STAGE_MACOS -eq 'True') { 'macOS' }
|
||||
if ($env:VSCODE_BUILD_STAGE_WEB -eq 'True') { 'Web' }
|
||||
)
|
||||
Write-Host "Stages to check: $stages"
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ function main() {
|
|||
.pipe(merge({
|
||||
fileName: 'combined.nls.metadata.json',
|
||||
jsonSpace: '',
|
||||
concatArrays: true,
|
||||
edit: (parsedJson, file) => {
|
||||
if (file.base === 'out-vscode-web-min') {
|
||||
return { vscode: parsedJson };
|
||||
|
|
|
@ -33,6 +33,7 @@ function main(): Promise<void> {
|
|||
.pipe(merge({
|
||||
fileName: 'combined.nls.metadata.json',
|
||||
jsonSpace: '',
|
||||
concatArrays: true,
|
||||
edit: (parsedJson, file) => {
|
||||
if (file.base === 'out-vscode-web-min') {
|
||||
return { vscode: parsedJson };
|
||||
|
|
|
@ -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 }}
|
||||
|
|
|
@ -294,6 +294,10 @@
|
|||
"name": "vs/workbench/contrib/audioCues",
|
||||
"project": "vscode-workbench"
|
||||
},
|
||||
{
|
||||
"name": "vs/workbench/contrib/deprecatedExtensionMigrator",
|
||||
"project": "vscode-workbench"
|
||||
},
|
||||
{
|
||||
"name": "vs/workbench/contrib/offline",
|
||||
"project": "vscode-workbench"
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
"scmActionButton",
|
||||
"scmSelectedProvider",
|
||||
"scmValidation",
|
||||
"tabInputTextMerge",
|
||||
"timeline"
|
||||
],
|
||||
"categories": [
|
||||
|
|
|
@ -221,10 +221,10 @@
|
|||
"config.timeline.date.committed": "Use the committed date",
|
||||
"config.timeline.date.authored": "Use the authored date",
|
||||
"config.useCommitInputAsStashMessage": "Controls whether to use the message from the commit input box as the default stash message.",
|
||||
"config.showActionButton": "Controls whether an action button can be shown in the Source Control view.",
|
||||
"config.showActionButton.commit": "Show an action button to commit changes.",
|
||||
"config.showActionButton.publish": "Show an action button to publish a local branch.",
|
||||
"config.showActionButton.sync": "Show an action button to sync changes.",
|
||||
"config.showActionButton": "Controls whether an action button is shown in the Source Control view.",
|
||||
"config.showActionButton.commit": "Show an action button to commit changes when the local branch has modified files ready to be committed.",
|
||||
"config.showActionButton.publish": "Show an action button to publish the local branch when it does not have a tracking remote branch.",
|
||||
"config.showActionButton.sync": "Show an action button to synchronize changes when the local branch is either ahead or behind the remote branch.",
|
||||
"config.statusLimit": "Controls how to limit the number of changes that can be parsed from Git status command. Can be set to 0 for no limit.",
|
||||
"config.experimental.installGuide": "Experimental improvements for the git setup flow.",
|
||||
"config.repositoryScanIgnoredFolders": "List of folders that are ignored while scanning for Git repositories when `#git.autoRepositoryDetection#` is set to `true` or `subFolders`.",
|
||||
|
|
|
@ -50,6 +50,8 @@ export class ActionButtonCommand {
|
|||
repository.onDidRunGitStatus(this.onDidRunGitStatus, this, this.disposables);
|
||||
repository.onDidChangeOperations(this.onDidChangeOperations, this, this.disposables);
|
||||
|
||||
this.disposables.push(postCommitCommandsProviderRegistry.onDidChangePostCommitCommandsProviders(() => this._onDidChange.fire()));
|
||||
|
||||
const root = Uri.file(repository.root);
|
||||
this.disposables.push(workspace.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('git.enableSmartCommit', root) ||
|
||||
|
@ -189,10 +191,10 @@ export class ActionButtonCommand {
|
|||
return {
|
||||
command: {
|
||||
command: 'git.publish',
|
||||
title: localize('scm publish branch action button title', "{0} Publish Branch", '$(cloud-upload)'),
|
||||
title: localize({ key: 'scm publish branch action button title', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }, "{0} Publish Branch", '$(cloud-upload)'),
|
||||
tooltip: this.state.isSyncInProgress ?
|
||||
localize('scm button publish branch running', "Publishing Branch...") :
|
||||
localize('scm button publish branch', "Publish Branch"),
|
||||
localize({ key: 'scm button publish branch running', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }, "Publishing Branch...") :
|
||||
localize({ key: 'scm button publish branch', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }, "Publish Branch"),
|
||||
arguments: [this.repository.sourceControl],
|
||||
},
|
||||
enabled: !this.state.isSyncInProgress
|
||||
|
@ -202,19 +204,18 @@ export class ActionButtonCommand {
|
|||
private getSyncChangesActionButton(): SourceControlActionButton | undefined {
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
|
||||
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, commit/merge is in progress, or the button is disabled
|
||||
if (!this.state.HEAD?.upstream || 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 is in progress, or the button is disabled
|
||||
if (!this.state.HEAD?.upstream || !branchIsAheadOrBehind || this.state.isCommitInProgress || this.state.isMergeInProgress || !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)` : '';
|
||||
const icon = this.state.isSyncInProgress ? '$(sync~spin)' : '$(sync)';
|
||||
|
||||
const rebaseWhenSync = config.get<string>('rebaseWhenSync');
|
||||
|
||||
return {
|
||||
command: {
|
||||
command: rebaseWhenSync ? 'git.syncRebase' : 'git.sync',
|
||||
command: 'git.sync',
|
||||
title: `${icon}${behind}${ahead}`,
|
||||
tooltip: this.state.isSyncInProgress ?
|
||||
localize('syncing changes', "Synchronizing Changes...")
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText } from 'vscode';
|
||||
import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge } from 'vscode';
|
||||
import TelemetryReporter from '@vscode/extension-telemetry';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator';
|
||||
|
@ -53,7 +53,7 @@ class CheckoutTagItem extends CheckoutItem {
|
|||
|
||||
class CheckoutRemoteHeadItem extends CheckoutItem {
|
||||
|
||||
override get label(): string { return `$(git-branch) ${this.ref.name || this.shortCommit}`; }
|
||||
override get label(): string { return `$(cloud) ${this.ref.name || this.shortCommit}`; }
|
||||
override get description(): string {
|
||||
return localize('remote branch at', "Remote branch at {0}", this.shortCommit);
|
||||
}
|
||||
|
@ -418,6 +418,7 @@ export class CommandCenter {
|
|||
return;
|
||||
}
|
||||
|
||||
const isRebasing = Boolean(repo.rebaseCommit);
|
||||
|
||||
type InputData = { uri: Uri; title?: string; detail?: string; description?: string };
|
||||
const mergeUris = toMergeUris(uri);
|
||||
|
@ -425,14 +426,17 @@ export class CommandCenter {
|
|||
const theirs: InputData = { uri: mergeUris.theirs, title: localize('Theirs', 'Theirs') };
|
||||
|
||||
try {
|
||||
const [head, mergeHead] = await Promise.all([repo.getCommit('HEAD'), repo.getCommit('MERGE_HEAD')]);
|
||||
const [head, rebaseOrMergeHead] = await Promise.all([
|
||||
repo.getCommit('HEAD'),
|
||||
isRebasing ? repo.getCommit('REBASE_HEAD') : repo.getCommit('MERGE_HEAD')
|
||||
]);
|
||||
// ours (current branch and commit)
|
||||
ours.detail = head.refNames.map(s => s.replace(/^HEAD ->/, '')).join(', ');
|
||||
ours.description = head.hash.substring(0, 7);
|
||||
|
||||
// theirs
|
||||
theirs.detail = mergeHead.refNames.join(', ');
|
||||
theirs.description = mergeHead.hash.substring(0, 7);
|
||||
theirs.detail = rebaseOrMergeHead.refNames.join(', ');
|
||||
theirs.description = rebaseOrMergeHead.hash.substring(0, 7);
|
||||
|
||||
} catch (error) {
|
||||
// not so bad, can continue with just uris
|
||||
|
@ -442,8 +446,8 @@ export class CommandCenter {
|
|||
|
||||
const options = {
|
||||
base: mergeUris.base,
|
||||
input1: theirs,
|
||||
input2: ours,
|
||||
input1: isRebasing ? ours : theirs,
|
||||
input2: isRebasing ? theirs : ours,
|
||||
output: uri
|
||||
};
|
||||
|
||||
|
@ -1099,21 +1103,26 @@ export class CommandCenter {
|
|||
return;
|
||||
}
|
||||
|
||||
const { activeTab } = window.tabGroups.activeTabGroup;
|
||||
if (!activeTab) {
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure to save the merged document
|
||||
const doc = workspace.textDocuments.find(doc => doc.uri.toString() === uri.toString());
|
||||
if (!doc) {
|
||||
console.log(`FAILED to accept merge because uri ${uri.toString()} doesn't match a document`);
|
||||
return;
|
||||
}
|
||||
if (doc.isDirty) {
|
||||
await doc.save();
|
||||
}
|
||||
|
||||
await doc.save();
|
||||
|
||||
// TODO@jrieken there isn't a `TabInputTextMerge` instance yet, till now the merge editor
|
||||
// uses the `TabInputText` for the out-resource and we use that to identify and CLOSE the tab
|
||||
// see https://github.com/microsoft/vscode/issues/153213
|
||||
const { activeTab } = window.tabGroups.activeTabGroup;
|
||||
// find the merge editor tabs for the resource in question and close them all
|
||||
let didCloseTab = false;
|
||||
if (activeTab && activeTab?.input instanceof TabInputText && activeTab.input.uri.toString() === uri.toString()) {
|
||||
didCloseTab = await window.tabGroups.close(activeTab, true);
|
||||
const mergeEditorTabs = window.tabGroups.all.map(group => group.tabs.filter(tab => tab.input instanceof TabInputTextMerge && tab.input.result.toString() === uri.toString())).flat();
|
||||
if (mergeEditorTabs.includes(activeTab)) {
|
||||
didCloseTab = await window.tabGroups.close(mergeEditorTabs, true);
|
||||
}
|
||||
|
||||
// Only stage if the merge editor has been successfully closed. That means all conflicts have been
|
||||
|
@ -1443,7 +1452,7 @@ export class CommandCenter {
|
|||
private async smartCommit(
|
||||
repository: Repository,
|
||||
getCommitMessage: () => Promise<string | undefined>,
|
||||
opts?: CommitOptions
|
||||
opts: CommitOptions
|
||||
): Promise<boolean> {
|
||||
const config = workspace.getConfiguration('git', Uri.file(repository.root));
|
||||
let promptToSaveFilesBeforeCommit = config.get<'always' | 'staged' | 'never'>('promptToSaveFilesBeforeCommit');
|
||||
|
@ -1489,14 +1498,8 @@ export class CommandCenter {
|
|||
}
|
||||
}
|
||||
|
||||
if (!opts) {
|
||||
opts = { all: noStagedChanges };
|
||||
} else if (!opts.all && noStagedChanges && !opts.empty) {
|
||||
opts = { ...opts, all: true };
|
||||
}
|
||||
|
||||
// no changes, and the user has not configured to commit all in this case
|
||||
if (!noUnstagedChanges && noStagedChanges && !enableSmartCommit && !opts.empty) {
|
||||
if (!noUnstagedChanges && noStagedChanges && !enableSmartCommit && !opts.empty && !opts.all) {
|
||||
const suggestSmartCommit = config.get<boolean>('suggestSmartCommit') === true;
|
||||
|
||||
if (!suggestSmartCommit) {
|
||||
|
@ -1520,6 +1523,12 @@ export class CommandCenter {
|
|||
}
|
||||
}
|
||||
|
||||
if (opts.all === undefined) {
|
||||
opts = { all: noStagedChanges };
|
||||
} else if (!opts.all && noStagedChanges && !opts.empty) {
|
||||
opts = { ...opts, all: true };
|
||||
}
|
||||
|
||||
// enable signing of commits if configured
|
||||
opts.signCommit = enableCommitSigning;
|
||||
|
||||
|
@ -1633,7 +1642,7 @@ export class CommandCenter {
|
|||
return true;
|
||||
}
|
||||
|
||||
private async commitWithAnyInput(repository: Repository, opts?: CommitOptions): Promise<void> {
|
||||
private async commitWithAnyInput(repository: Repository, opts: CommitOptions): Promise<void> {
|
||||
const message = repository.inputBox.value;
|
||||
const root = Uri.file(repository.root);
|
||||
const config = workspace.getConfiguration('git', root);
|
||||
|
@ -2566,17 +2575,16 @@ export class CommandCenter {
|
|||
}
|
||||
}
|
||||
|
||||
if (rebase) {
|
||||
await repository.syncRebase(HEAD);
|
||||
} else {
|
||||
await repository.sync(HEAD);
|
||||
}
|
||||
await repository.sync(HEAD, rebase);
|
||||
}
|
||||
|
||||
@command('git.sync', { repository: true })
|
||||
async sync(repository: Repository): Promise<void> {
|
||||
const config = workspace.getConfiguration('git', Uri.file(repository.root));
|
||||
const rebase = config.get<boolean>('rebaseWhenSync', false) === true;
|
||||
|
||||
try {
|
||||
await this._sync(repository, false);
|
||||
await this._sync(repository, rebase);
|
||||
} catch (err) {
|
||||
if (/Cancelled/i.test(err && (err.message || err.stderr || ''))) {
|
||||
return;
|
||||
|
@ -2589,13 +2597,16 @@ export class CommandCenter {
|
|||
@command('git._syncAll')
|
||||
async syncAll(): Promise<void> {
|
||||
await Promise.all(this.model.repositories.map(async repository => {
|
||||
const config = workspace.getConfiguration('git', Uri.file(repository.root));
|
||||
const rebase = config.get<boolean>('rebaseWhenSync', false) === true;
|
||||
|
||||
const HEAD = repository.HEAD;
|
||||
|
||||
if (!HEAD || !HEAD.upstream) {
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.sync(HEAD);
|
||||
await repository.sync(HEAD, rebase);
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
@ -108,6 +108,9 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand
|
|||
|
||||
private postCommitCommandsProviders = new Set<PostCommitCommandsProvider>();
|
||||
|
||||
private _onDidChangePostCommitCommandsProviders = new EventEmitter<void>();
|
||||
readonly onDidChangePostCommitCommandsProviders = this._onDidChangePostCommitCommandsProviders.event;
|
||||
|
||||
private showRepoOnHomeDriveRootWarning = true;
|
||||
private pushErrorHandlers = new Set<PushErrorHandler>();
|
||||
|
||||
|
@ -591,8 +594,12 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand
|
|||
|
||||
registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable {
|
||||
this.postCommitCommandsProviders.add(provider);
|
||||
this._onDidChangePostCommitCommandsProviders.fire();
|
||||
|
||||
return toDisposable(() => this.postCommitCommandsProviders.delete(provider));
|
||||
return toDisposable(() => {
|
||||
this.postCommitCommandsProviders.delete(provider);
|
||||
this._onDidChangePostCommitCommandsProviders.fire();
|
||||
});
|
||||
}
|
||||
|
||||
getPostCommitCommandsProviders(): PostCommitCommandsProvider[] {
|
||||
|
|
|
@ -4,10 +4,12 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
import { Command, Disposable } from 'vscode';
|
||||
import { Command, Disposable, Event } from 'vscode';
|
||||
import { PostCommitCommandsProvider } from './api/git';
|
||||
|
||||
export interface IPostCommitCommandsProviderRegistry {
|
||||
readonly onDidChangePostCommitCommandsProviders: Event<void>;
|
||||
|
||||
getPostCommitCommandsProviders(): PostCommitCommandsProvider[];
|
||||
registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable;
|
||||
}
|
||||
|
|
|
@ -948,7 +948,6 @@ export class Repository implements Disposable {
|
|||
|| e.affectsConfiguration('git.untrackedChanges', root)
|
||||
|| e.affectsConfiguration('git.ignoreSubmodules', root)
|
||||
|| e.affectsConfiguration('git.openDiffOnClick', root)
|
||||
|| e.affectsConfiguration('git.rebaseWhenSync', root)
|
||||
|| e.affectsConfiguration('git.showActionButton', root)
|
||||
)(this.updateModelState, this, this.disposables);
|
||||
|
||||
|
@ -1510,13 +1509,8 @@ export class Repository implements Disposable {
|
|||
}
|
||||
|
||||
@throttle
|
||||
sync(head: Branch): Promise<void> {
|
||||
return this._sync(head, false);
|
||||
}
|
||||
|
||||
@throttle
|
||||
async syncRebase(head: Branch): Promise<void> {
|
||||
return this._sync(head, true);
|
||||
sync(head: Branch, rebase: boolean): Promise<void> {
|
||||
return this._sync(head, rebase);
|
||||
}
|
||||
|
||||
private async _sync(head: Branch, rebase: boolean): Promise<void> {
|
||||
|
|
|
@ -145,10 +145,7 @@ class SyncStatusBar {
|
|||
text += this.repository.syncLabel;
|
||||
}
|
||||
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
|
||||
const rebaseWhenSync = config.get<string>('rebaseWhenSync');
|
||||
|
||||
command = rebaseWhenSync ? 'git.syncRebase' : 'git.sync';
|
||||
command = 'git.sync';
|
||||
tooltip = this.repository.syncTooltip;
|
||||
} else {
|
||||
icon = '$(cloud-upload)';
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
"../../src/vscode-dts/vscode.proposed.scmActionButton.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.scmValidation.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.tabs.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.tabInputTextMerge.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.timeline.d.ts",
|
||||
"../types/lib.textEncoder.d.ts"
|
||||
]
|
||||
|
|
|
@ -26,7 +26,8 @@
|
|||
}
|
||||
},
|
||||
"enabledApiProposals": [
|
||||
"contribShareMenu"
|
||||
"contribShareMenu",
|
||||
"contribEditSessions"
|
||||
],
|
||||
"contributes": {
|
||||
"commands": [
|
||||
|
@ -37,10 +38,20 @@
|
|||
{
|
||||
"command": "github.copyVscodeDevLink",
|
||||
"title": "Copy vscode.dev Link"
|
||||
},
|
||||
{
|
||||
"command": "github.copyVscodeDevLinkFile",
|
||||
"title": "Copy vscode.dev Link"
|
||||
},
|
||||
{
|
||||
"command": "github.copyVscodeDevLinkFile",
|
||||
"title": "Copy vscode.dev Link"
|
||||
},
|
||||
{
|
||||
"command": "github.openOnVscodeDev",
|
||||
"title": "Open on vscode.dev"
|
||||
}
|
||||
],
|
||||
"continueEditSession": [
|
||||
{
|
||||
"command": "github.openOnVscodeDev",
|
||||
"when": "github.hasGitHubRepo"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
|
@ -56,6 +67,10 @@
|
|||
{
|
||||
"command": "github.copyVscodeDevLinkFile",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "github.openOnVscodeDev",
|
||||
"when": "false"
|
||||
}
|
||||
],
|
||||
"file/share": [
|
||||
|
|
|
@ -20,6 +20,16 @@ async function copyVscodeDevLink(gitAPI: GitAPI, useSelection: boolean) {
|
|||
}
|
||||
}
|
||||
|
||||
async function openVscodeDevLink(gitAPI: GitAPI): Promise<vscode.Uri | undefined> {
|
||||
try {
|
||||
const permalink = getPermalink(gitAPI, true, 'https://vscode.dev/github');
|
||||
return permalink ? vscode.Uri.parse(permalink) : undefined;
|
||||
} catch (err) {
|
||||
vscode.window.showErrorMessage(err.message);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function registerCommands(gitAPI: GitAPI): vscode.Disposable {
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
|
@ -39,5 +49,9 @@ export function registerCommands(gitAPI: GitAPI): vscode.Disposable {
|
|||
return copyVscodeDevLink(gitAPI, false);
|
||||
}));
|
||||
|
||||
disposables.add(vscode.commands.registerCommand('github.openOnVscodeDev', async () => {
|
||||
return openVscodeDevLink(gitAPI);
|
||||
}));
|
||||
|
||||
return disposables;
|
||||
}
|
||||
|
|
|
@ -37,7 +37,8 @@
|
|||
".editorconfig"
|
||||
],
|
||||
"filenames": [
|
||||
"gitconfig"
|
||||
"gitconfig",
|
||||
".env"
|
||||
],
|
||||
"filenamePatterns": [
|
||||
"**/.config/git/config",
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
"Programming Languages"
|
||||
],
|
||||
"enabledApiProposals": [
|
||||
"textEditorDrop",
|
||||
"documentPaste"
|
||||
],
|
||||
"activationEvents": [
|
||||
|
@ -563,7 +562,8 @@
|
|||
"@types/picomatch": "^2.3.0",
|
||||
"@types/vscode-notebook-renderer": "^1.60.0",
|
||||
"@types/vscode-webview": "^1.57.0",
|
||||
"lodash.throttle": "^4.1.1"
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"vscode-languageserver-types": "^3.17.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
"configuration.markdown.links.openLocation.currentGroup": "Open links in the active editor group.",
|
||||
"configuration.markdown.links.openLocation.beside": "Open links beside the active editor.",
|
||||
"configuration.markdown.suggest.paths.enabled.description": "Enable/disable path suggestions for markdown links",
|
||||
"configuration.markdown.editor.drop.enabled": "Enable/disable dropping into the markdown editor to insert shift. Requires enabling `#workbench.experimental.editor.dropIntoEditor.enabled#`.",
|
||||
"configuration.markdown.editor.drop.enabled": "Enable/disable dropping into the markdown editor to insert shift. Requires enabling `#workbench.editor.dropIntoEditor.enabled#`.",
|
||||
"configuration.markdown.editor.pasteLinks.enabled": "Enable/disable pasting files into a Markdown editor inserts Markdown links. Requires enabling `#editor.experimental.pasteActions.enabled#`.",
|
||||
"configuration.markdown.experimental.validate.enabled.description": "Enable/disable all error reporting in Markdown files.",
|
||||
"configuration.markdown.experimental.validate.referenceLinks.enabled.description": "Validate reference links in Markdown files, e.g. `[link][ref]`. Requires enabling `#markdown.experimental.validate.enabled#`.",
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"name": "Attach",
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"port": 7675,
|
||||
"port": 7692,
|
||||
"sourceMaps": true,
|
||||
"outFiles": ["${workspaceFolder}/out/**/*.js"]
|
||||
}
|
||||
|
|
|
@ -10,17 +10,16 @@
|
|||
"main": "./out/node/main",
|
||||
"browser": "./dist/browser/main",
|
||||
"dependencies": {
|
||||
"vscode-languageserver": "^8.0.2-next.4",
|
||||
"vscode-uri": "^3.0.3",
|
||||
"vscode-languageserver": "^8.0.2-next.5`",
|
||||
"vscode-languageserver-textdocument": "^1.0.5",
|
||||
"vscode-languageserver-types": "^3.17.1",
|
||||
"vscode-markdown-languageservice": "microsoft/vscode-markdown-languageservice"
|
||||
"vscode-markdown-languageservice": "^0.0.0-alpha.8",
|
||||
"vscode-uri": "^3.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "16.x"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "cd node_modules/vscode-markdown-languageservice && yarn run compile-ext",
|
||||
"compile": "gulp compile-extension:markdown-language-features-server",
|
||||
"watch": "gulp watch-extension:markdown-language-features-server"
|
||||
}
|
||||
|
|
24
extensions/markdown-language-features/server/src/config.ts
Normal file
24
extensions/markdown-language-features/server/src/config.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.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export interface LsConfiguration {
|
||||
/**
|
||||
* List of file extensions should be considered as markdown.
|
||||
*
|
||||
* These should not include the leading `.`.
|
||||
*/
|
||||
readonly markdownFileExtensions: readonly string[];
|
||||
}
|
||||
|
||||
const defaultConfig: LsConfiguration = {
|
||||
markdownFileExtensions: ['md'],
|
||||
};
|
||||
|
||||
export function getLsConfiguration(overrides: Partial<LsConfiguration>): LsConfiguration {
|
||||
return {
|
||||
...defaultConfig,
|
||||
...overrides,
|
||||
};
|
||||
}
|
|
@ -5,11 +5,14 @@
|
|||
|
||||
import { RequestType } from 'vscode-languageserver';
|
||||
import * as md from 'vscode-markdown-languageservice';
|
||||
import * as lsp from 'vscode-languageserver-types';
|
||||
|
||||
declare const TextDecoder: any;
|
||||
|
||||
// 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');
|
||||
|
||||
// To server
|
||||
export const getReferencesToFileInWorkspace = new RequestType<{ uri: string }, lsp.Location[], any>('markdown/getReferencesToFileInWorkspace');
|
||||
|
|
|
@ -3,48 +3,92 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Connection, InitializeParams, InitializeResult, TextDocuments } from 'vscode-languageserver';
|
||||
import { CancellationToken, Connection, InitializeParams, InitializeResult, NotebookDocuments, TextDocuments } from 'vscode-languageserver';
|
||||
import { TextDocument } from 'vscode-languageserver-textdocument';
|
||||
import * as lsp from 'vscode-languageserver-types';
|
||||
import * as md from 'vscode-markdown-languageservice';
|
||||
import { URI } from 'vscode-uri';
|
||||
import { getLsConfiguration } from './config';
|
||||
import { LogFunctionLogger } from './logging';
|
||||
import { parseRequestType } from './protocol';
|
||||
import * as protocol from './protocol';
|
||||
import { VsCodeClientWorkspace } from './workspace';
|
||||
|
||||
declare const TextDecoder: any;
|
||||
|
||||
export function startServer(connection: Connection) {
|
||||
export async function startServer(connection: Connection) {
|
||||
const documents = new TextDocuments(TextDocument);
|
||||
documents.listen(connection);
|
||||
const notebooks = new NotebookDocuments(documents);
|
||||
|
||||
connection.onInitialize((_params: InitializeParams): InitializeResult => {
|
||||
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() });
|
||||
}
|
||||
};
|
||||
|
||||
const config = getLsConfiguration({
|
||||
markdownFileExtensions: params.initializationOptions.markdownFileExtensions,
|
||||
});
|
||||
|
||||
const workspace = new VsCodeClientWorkspace(connection, config, documents, notebooks);
|
||||
const logger = new LogFunctionLogger(connection.console.log.bind(connection.console));
|
||||
provider = md.createLanguageService({
|
||||
workspace,
|
||||
parser,
|
||||
logger,
|
||||
markdownFileExtensions: config.markdownFileExtensions,
|
||||
});
|
||||
|
||||
workspace.workspaceFolders = (params.workspaceFolders ?? []).map(x => URI.parse(x.uri));
|
||||
return {
|
||||
capabilities: {
|
||||
completionProvider: { triggerCharacters: ['.', '/', '#'] },
|
||||
definitionProvider: true,
|
||||
documentLinkProvider: { resolveProvider: true },
|
||||
documentSymbolProvider: true,
|
||||
foldingRangeProvider: true,
|
||||
renameProvider: { prepareProvider: true, },
|
||||
selectionRangeProvider: true,
|
||||
workspaceSymbolProvider: true,
|
||||
workspace: {
|
||||
workspaceFolders: {
|
||||
supported: true,
|
||||
changeNotifications: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const parser = new class implements md.IMdParser {
|
||||
slugifier = md.githubSlugifier;
|
||||
|
||||
async tokenize(document: md.ITextDocument): Promise<md.Token[]> {
|
||||
return await connection.sendRequest(parseRequestType, { uri: document.uri.toString() });
|
||||
let provider: md.IMdLanguageService | undefined;
|
||||
|
||||
connection.onDocumentLinks(async (params, token): Promise<lsp.DocumentLink[]> => {
|
||||
try {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
if (document) {
|
||||
return await provider!.getDocumentLinks(document, token);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e.stack);
|
||||
}
|
||||
};
|
||||
return [];
|
||||
});
|
||||
|
||||
const workspace = new VsCodeClientWorkspace(connection, documents);
|
||||
const logger = new LogFunctionLogger(connection.console.log.bind(connection.console));
|
||||
const provider = md.createLanguageService({ workspace, parser, logger });
|
||||
connection.onDocumentLinkResolve(async (link, token): Promise<lsp.DocumentLink | undefined> => {
|
||||
try {
|
||||
return await provider!.resolveDocumentLink(link, token);
|
||||
} catch (e) {
|
||||
console.error(e.stack);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
connection.onDocumentSymbol(async (params, token): Promise<lsp.DocumentSymbol[]> => {
|
||||
try {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
if (document) {
|
||||
return await provider.provideDocumentSymbols(document, token);
|
||||
return await provider!.getDocumentSymbols(document, token);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e.stack);
|
||||
|
@ -56,7 +100,7 @@ export function startServer(connection: Connection) {
|
|||
try {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
if (document) {
|
||||
return await provider.provideFoldingRanges(document, token);
|
||||
return await provider!.getFoldingRanges(document, token);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e.stack);
|
||||
|
@ -68,7 +112,7 @@ export function startServer(connection: Connection) {
|
|||
try {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
if (document) {
|
||||
return await provider.provideSelectionRanges(document, params.positions, token);
|
||||
return await provider!.getSelectionRanges(document, params.positions, token);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e.stack);
|
||||
|
@ -78,13 +122,85 @@ export function startServer(connection: Connection) {
|
|||
|
||||
connection.onWorkspaceSymbol(async (params, token): Promise<lsp.WorkspaceSymbol[]> => {
|
||||
try {
|
||||
return await provider.provideWorkspaceSymbols(params.query, token);
|
||||
return await provider!.getWorkspaceSymbols(params.query, token);
|
||||
} catch (e) {
|
||||
console.error(e.stack);
|
||||
}
|
||||
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);
|
||||
if (document) {
|
||||
return await provider!.getReferences(document, params.position, params.context, token);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e.stack);
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
connection.onDefinition(async (params, token): Promise<lsp.Definition | undefined> => {
|
||||
try {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
if (document) {
|
||||
return await provider!.getDefinition(document, params.position, token);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e.stack);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
connection.onPrepareRename(async (params, token) => {
|
||||
try {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
if (document) {
|
||||
return await provider!.prepareRename(document, params.position, token);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e.stack);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
connection.onRenameRequest(async (params, token) => {
|
||||
try {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
if (document) {
|
||||
const edit = await provider!.getRenameEdit(document, params.position, params.newName, token);
|
||||
console.log(JSON.stringify(edit));
|
||||
return edit;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e.stack);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
connection.onRequest(protocol.getReferencesToFileInWorkspace, (async (params: { uri: string }, token: CancellationToken) => {
|
||||
try {
|
||||
return await provider!.getFileReferences(URI.parse(params.uri), token);
|
||||
} catch (e) {
|
||||
console.error(e.stack);
|
||||
}
|
||||
return undefined;
|
||||
}));
|
||||
|
||||
documents.listen(connection);
|
||||
notebooks.listen(connection);
|
||||
connection.listen();
|
||||
}
|
||||
|
||||
|
|
|
@ -4,24 +4,13 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TextDocument } from 'vscode-languageserver-textdocument';
|
||||
import * as URI from 'vscode-uri';
|
||||
import { URI, Utils } from 'vscode-uri';
|
||||
import { LsConfiguration } from '../config';
|
||||
|
||||
const markdownFileExtensions = Object.freeze<string[]>([
|
||||
'.md',
|
||||
'.mkd',
|
||||
'.mdwn',
|
||||
'.mdown',
|
||||
'.markdown',
|
||||
'.markdn',
|
||||
'.mdtxt',
|
||||
'.mdtext',
|
||||
'.workbook',
|
||||
]);
|
||||
|
||||
export function looksLikeMarkdownPath(resolvedHrefPath: URI.URI) {
|
||||
return markdownFileExtensions.includes(URI.Utils.extname(URI.URI.from(resolvedHrefPath)).toLowerCase());
|
||||
export function looksLikeMarkdownPath(config: LsConfiguration, resolvedHrefPath: URI) {
|
||||
return config.markdownFileExtensions.includes(Utils.extname(URI.from(resolvedHrefPath)).toLowerCase().replace('.', ''));
|
||||
}
|
||||
|
||||
export function isMarkdownDocument(document: TextDocument): boolean {
|
||||
export function isMarkdownFile(document: TextDocument) {
|
||||
return document.languageId === 'markdown';
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export const Schemes = Object.freeze({
|
||||
notebookCell: 'vscode-notebook-cell',
|
||||
});
|
|
@ -3,15 +3,18 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Connection, Emitter, FileChangeType, TextDocuments } from 'vscode-languageserver';
|
||||
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 { URI } from 'vscode-uri';
|
||||
import { LsConfiguration } from './config';
|
||||
import * as protocol from './protocol';
|
||||
import { coalesce } from './util/arrays';
|
||||
import { isMarkdownDocument, looksLikeMarkdownPath } from './util/file';
|
||||
import { isMarkdownFile, looksLikeMarkdownPath } from './util/file';
|
||||
import { Limiter } from './util/limiter';
|
||||
import { ResourceMap } from './util/resourceMap';
|
||||
import { Schemes } from './util/schemes';
|
||||
|
||||
declare const TextDecoder: any;
|
||||
|
||||
|
@ -32,7 +35,9 @@ export class VsCodeClientWorkspace implements md.IWorkspace {
|
|||
|
||||
constructor(
|
||||
private readonly connection: Connection,
|
||||
private readonly config: LsConfiguration,
|
||||
private readonly documents: TextDocuments<TextDocument>,
|
||||
private readonly notebooks: NotebookDocuments<TextDocument>,
|
||||
) {
|
||||
documents.onDidOpen(e => {
|
||||
this._documentCache.delete(URI.parse(e.document.uri));
|
||||
|
@ -57,14 +62,14 @@ export class VsCodeClientWorkspace implements md.IWorkspace {
|
|||
switch (change.type) {
|
||||
case FileChangeType.Changed: {
|
||||
this._documentCache.delete(resource);
|
||||
const document = await this.getOrLoadMarkdownDocument(resource);
|
||||
const document = await this.openMarkdownDocument(resource);
|
||||
if (document) {
|
||||
this._onDidChangeMarkdownDocument.fire(document);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FileChangeType.Created: {
|
||||
const document = await this.getOrLoadMarkdownDocument(resource);
|
||||
const document = await this.openMarkdownDocument(resource);
|
||||
if (document) {
|
||||
this._onDidCreateMarkdownDocument.fire(document);
|
||||
}
|
||||
|
@ -80,6 +85,22 @@ export class VsCodeClientWorkspace implements md.IWorkspace {
|
|||
});
|
||||
}
|
||||
|
||||
public listen() {
|
||||
this.connection.workspace.onDidChangeWorkspaceFolders(async () => {
|
||||
this.workspaceFolders = (await this.connection.workspace.getWorkspaceFolders() ?? []).map(x => URI.parse(x.uri));
|
||||
});
|
||||
}
|
||||
|
||||
private _workspaceFolders: readonly URI[] = [];
|
||||
|
||||
get workspaceFolders(): readonly URI[] {
|
||||
return this._workspaceFolders;
|
||||
}
|
||||
|
||||
set workspaceFolders(value: readonly URI[]) {
|
||||
this._workspaceFolders = value;
|
||||
}
|
||||
|
||||
async getAllMarkdownDocuments(): Promise<Iterable<md.ITextDocument>> {
|
||||
const maxConcurrent = 20;
|
||||
|
||||
|
@ -91,7 +112,7 @@ export class VsCodeClientWorkspace implements md.IWorkspace {
|
|||
const onDiskResults = await Promise.all(resources.map(strResource => {
|
||||
return limiter.queue(async () => {
|
||||
const resource = URI.parse(strResource);
|
||||
const doc = await this.getOrLoadMarkdownDocument(resource);
|
||||
const doc = await this.openMarkdownDocument(resource);
|
||||
if (doc) {
|
||||
foundFiles.set(resource);
|
||||
}
|
||||
|
@ -110,7 +131,7 @@ export class VsCodeClientWorkspace implements md.IWorkspace {
|
|||
return !!this.documents.get(resource.toString());
|
||||
}
|
||||
|
||||
async getOrLoadMarkdownDocument(resource: URI): Promise<md.ITextDocument | undefined> {
|
||||
async openMarkdownDocument(resource: URI): Promise<md.ITextDocument | undefined> {
|
||||
const existing = this._documentCache.get(resource);
|
||||
if (existing) {
|
||||
return existing;
|
||||
|
@ -122,7 +143,7 @@ export class VsCodeClientWorkspace implements md.IWorkspace {
|
|||
return matchingDocument;
|
||||
}
|
||||
|
||||
if (!looksLikeMarkdownPath(resource)) {
|
||||
if (!looksLikeMarkdownPath(this.config, resource)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
@ -141,15 +162,31 @@ export class VsCodeClientWorkspace implements md.IWorkspace {
|
|||
}
|
||||
}
|
||||
|
||||
async pathExists(_resource: URI): Promise<boolean> {
|
||||
return false;
|
||||
async stat(resource: URI): Promise<md.FileStat | undefined> {
|
||||
if (this._documentCache.has(resource) || this.documents.get(resource.toString())) {
|
||||
return { isDirectory: false };
|
||||
}
|
||||
return this.connection.sendRequest(protocol.statFileRequestType, { uri: resource.toString() });
|
||||
}
|
||||
|
||||
async readDirectory(_resource: URI): Promise<[string, { isDir: boolean }][]> {
|
||||
return [];
|
||||
async readDirectory(resource: URI): Promise<[string, md.FileStat][]> {
|
||||
return this.connection.sendRequest(protocol.readDirectoryRequestType, { uri: resource.toString() });
|
||||
}
|
||||
|
||||
getContainingDocument(resource: URI): ContainingDocumentContext | undefined {
|
||||
if (resource.scheme === Schemes.notebookCell) {
|
||||
const nb = this.notebooks.findNotebookDocumentForCell(resource.toString());
|
||||
if (nb) {
|
||||
return {
|
||||
uri: URI.parse(nb.uri),
|
||||
children: nb.cells.map(cell => ({ uri: URI.parse(cell.document) })),
|
||||
};
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private isRelevantMarkdownDocument(doc: TextDocument) {
|
||||
return isMarkdownDocument(doc) && URI.parse(doc.uri).scheme !== 'vscode-bulkeditpreview';
|
||||
return isMarkdownFile(doc) && URI.parse(doc.uri).scheme !== 'vscode-bulkeditpreview';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,17 +35,19 @@ vscode-languageserver-types@^3.17.1:
|
|||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.1.tgz#c2d87fa7784f8cac389deb3ff1e2d9a7bef07e16"
|
||||
integrity sha512-K3HqVRPElLZVVPtMeKlsyL9aK0GxGQpvtAUTfX4k7+iJ4mc1M+JM+zQwkgGy2LzY0f0IAafe8MKqIkJrxfGGjQ==
|
||||
|
||||
vscode-languageserver@^8.0.2-next.4:
|
||||
vscode-languageserver@^8.0.2-next.5`:
|
||||
version "8.0.2-next.5"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.0.2-next.5.tgz#39a2dd4c504fb88042375e7ac706a714bdaab4e5"
|
||||
integrity sha512-2ZDb7O/4atS9mJKufPPz637z+51kCyZfgnobFW5eSrUdS3c0UB/nMS4Ng1EavYTX84GVaVMKCrmP0f2ceLmR0A==
|
||||
dependencies:
|
||||
vscode-languageserver-protocol "3.17.2-next.6"
|
||||
|
||||
vscode-markdown-languageservice@microsoft/vscode-markdown-languageservice:
|
||||
version "0.0.0-alpha.2"
|
||||
resolved "https://codeload.github.com/microsoft/vscode-markdown-languageservice/tar.gz/db497ada376aae9a335519dbfb406c6a1f873446"
|
||||
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==
|
||||
dependencies:
|
||||
vscode-languageserver-textdocument "^1.0.5"
|
||||
vscode-languageserver-types "^3.17.1"
|
||||
vscode-uri "^3.0.3"
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import Token = require('markdown-it/lib/token');
|
||||
import * as vscode from 'vscode';
|
||||
import { BaseLanguageClient, LanguageClientOptions, RequestType } from 'vscode-languageclient';
|
||||
import { BaseLanguageClient, LanguageClientOptions, NotebookDocumentSyncRegistrationType, RequestType } from 'vscode-languageclient';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { IMdParser } from './markdownEngine';
|
||||
import { markdownFileExtensions } from './util/file';
|
||||
|
@ -14,9 +14,9 @@ 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;
|
||||
|
@ -24,22 +24,36 @@ export type LanguageClientConstructor = (name: string, description: string, clie
|
|||
|
||||
export async function startClient(factory: LanguageClientConstructor, workspace: IMdWorkspace, parser: IMdParser): Promise<BaseLanguageClient> {
|
||||
|
||||
const documentSelector = ['markdown'];
|
||||
const mdFileGlob = `**/*.{${markdownFileExtensions.join(',')}}`;
|
||||
|
||||
const clientOptions: LanguageClientOptions = {
|
||||
documentSelector,
|
||||
documentSelector: [{ language: 'markdown' }],
|
||||
synchronize: {
|
||||
configurationSection: ['markdown'],
|
||||
fileEvents: vscode.workspace.createFileSystemWatcher(mdFileGlob),
|
||||
},
|
||||
initializationOptions: {}
|
||||
initializationOptions: {
|
||||
markdownFileExtensions,
|
||||
}
|
||||
};
|
||||
|
||||
const client = factory('markdown', localize('markdownServer.name', 'Markdown Language Server'), clientOptions);
|
||||
|
||||
client.registerProposedFeatures();
|
||||
|
||||
const notebookFeature = client.getFeature(NotebookDocumentSyncRegistrationType.method);
|
||||
if (notebookFeature !== undefined) {
|
||||
notebookFeature.register({
|
||||
id: String(Date.now()),
|
||||
registerOptions: {
|
||||
notebookSelector: [{
|
||||
notebook: '*',
|
||||
cells: [{ language: 'markdown' }]
|
||||
}]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
client.onRequest(parseRequestType, async (e) => {
|
||||
const uri = vscode.Uri.parse(e.uri);
|
||||
const doc = await workspace.getOrLoadMarkdownDocument(uri);
|
||||
|
@ -55,6 +69,22 @@ export async function startClient(factory: LanguageClientConstructor, workspace:
|
|||
return Array.from(await vscode.workspace.fs.readFile(uri));
|
||||
});
|
||||
|
||||
client.onRequest(statFileRequestType, async (e): Promise<{ isDirectory: boolean } | undefined> => {
|
||||
const uri = vscode.Uri.parse(e.uri);
|
||||
try {
|
||||
const stat = await vscode.workspace.fs.stat(uri);
|
||||
return { isDirectory: stat.type === vscode.FileType.Directory };
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
client.onRequest(readDirectoryRequestType, 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[]> => {
|
||||
return (await vscode.workspace.findFiles(mdFileGlob, '**/node_modules/**')).map(x => x.toString());
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { LanguageClient, LanguageClientOptions } from 'vscode-languageclient/browser';
|
||||
import { BaseLanguageClient, LanguageClient, LanguageClientOptions } from 'vscode-languageclient/browser';
|
||||
import { startClient } from './client';
|
||||
import { activateShared } from './extension.shared';
|
||||
import { VsCodeOutputLogger } from './logging';
|
||||
|
@ -13,7 +13,7 @@ import { getMarkdownExtensionContributions } from './markdownExtensions';
|
|||
import { githubSlugifier } from './slugify';
|
||||
import { IMdWorkspace, VsCodeMdWorkspace } from './workspace';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
const contributions = getMarkdownExtensionContributions(context);
|
||||
context.subscriptions.push(contributions);
|
||||
|
||||
|
@ -25,15 +25,15 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
const workspace = new VsCodeMdWorkspace();
|
||||
context.subscriptions.push(workspace);
|
||||
|
||||
activateShared(context, workspace, engine, logger, contributions);
|
||||
startServer(context, workspace, engine);
|
||||
const client = await startServer(context, workspace, engine);
|
||||
activateShared(context, client, workspace, engine, logger, contributions);
|
||||
}
|
||||
|
||||
async function startServer(context: vscode.ExtensionContext, workspace: IMdWorkspace, parser: IMdParser): Promise<void> {
|
||||
function startServer(context: vscode.ExtensionContext, workspace: IMdWorkspace, parser: IMdParser): Promise<BaseLanguageClient> {
|
||||
const serverMain = vscode.Uri.joinPath(context.extensionUri, 'server/dist/browser/main.js');
|
||||
const worker = new Worker(serverMain.toString());
|
||||
|
||||
await startClient((id: string, name: string, clientOptions: LanguageClientOptions) => {
|
||||
return startClient((id: string, name: string, clientOptions: LanguageClientOptions) => {
|
||||
return new LanguageClient(id, name, clientOptions, worker);
|
||||
}, workspace, parser);
|
||||
}
|
||||
|
|
|
@ -4,17 +4,15 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { BaseLanguageClient } from 'vscode-languageclient';
|
||||
import { CommandManager } from './commandManager';
|
||||
import * as commands from './commands/index';
|
||||
import { registerPasteSupport } from './languageFeatures/copyPaste';
|
||||
import { registerDefinitionSupport } from './languageFeatures/definitions';
|
||||
import { registerDiagnosticSupport } from './languageFeatures/diagnostics';
|
||||
import { MdLinkProvider, registerDocumentLinkSupport } from './languageFeatures/documentLinks';
|
||||
import { MdLinkProvider } from './languageFeatures/documentLinks';
|
||||
import { registerDropIntoEditorSupport } from './languageFeatures/dropIntoEditor';
|
||||
import { registerFindFileReferenceSupport } from './languageFeatures/fileReferences';
|
||||
import { registerPathCompletionSupport } from './languageFeatures/pathCompletions';
|
||||
import { MdReferencesProvider, registerReferencesSupport } from './languageFeatures/references';
|
||||
import { registerRenameSupport } from './languageFeatures/rename';
|
||||
import { MdReferencesProvider } from './languageFeatures/references';
|
||||
import { ILogger } from './logging';
|
||||
import { IMdParser, MarkdownItEngine, MdParsingProvider } from './markdownEngine';
|
||||
import { MarkdownContributionProvider } from './markdownExtensions';
|
||||
|
@ -27,6 +25,7 @@ import { IMdWorkspace } from './workspace';
|
|||
|
||||
export function activateShared(
|
||||
context: vscode.ExtensionContext,
|
||||
client: BaseLanguageClient,
|
||||
workspace: IMdWorkspace,
|
||||
engine: MarkdownItEngine,
|
||||
logger: ILogger,
|
||||
|
@ -46,7 +45,7 @@ export function activateShared(
|
|||
const previewManager = new MarkdownPreviewManager(contentProvider, workspace, logger, contributions, tocProvider);
|
||||
context.subscriptions.push(previewManager);
|
||||
|
||||
context.subscriptions.push(registerMarkdownLanguageFeatures(parser, workspace, commandManager, tocProvider, logger));
|
||||
context.subscriptions.push(registerMarkdownLanguageFeatures(client, parser, workspace, commandManager, tocProvider, logger));
|
||||
context.subscriptions.push(registerMarkdownCommands(commandManager, previewManager, telemetryReporter, cspArbiter, engine, tocProvider));
|
||||
|
||||
context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => {
|
||||
|
@ -55,6 +54,7 @@ export function activateShared(
|
|||
}
|
||||
|
||||
function registerMarkdownLanguageFeatures(
|
||||
client: BaseLanguageClient,
|
||||
parser: IMdParser,
|
||||
workspace: IMdWorkspace,
|
||||
commandManager: CommandManager,
|
||||
|
@ -71,15 +71,10 @@ function registerMarkdownLanguageFeatures(
|
|||
referencesProvider,
|
||||
|
||||
// Language features
|
||||
registerDefinitionSupport(selector, referencesProvider),
|
||||
registerDiagnosticSupport(selector, workspace, linkProvider, commandManager, referencesProvider, tocProvider, logger),
|
||||
registerDocumentLinkSupport(selector, linkProvider),
|
||||
registerDropIntoEditorSupport(selector),
|
||||
registerFindFileReferenceSupport(commandManager, referencesProvider),
|
||||
registerFindFileReferenceSupport(commandManager, client),
|
||||
registerPasteSupport(selector),
|
||||
registerPathCompletionSupport(selector, workspace, parser, linkProvider),
|
||||
registerReferencesSupport(selector, referencesProvider),
|
||||
registerRenameSupport(selector, workspace, referencesProvider, parser.slugifier),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { LanguageClient, ServerOptions, TransportKind } from 'vscode-languageclient/node';
|
||||
import { BaseLanguageClient, LanguageClient, ServerOptions, TransportKind } from 'vscode-languageclient/node';
|
||||
import { startClient } from './client';
|
||||
import { activateShared } from './extension.shared';
|
||||
import { VsCodeOutputLogger } from './logging';
|
||||
|
@ -13,7 +13,7 @@ import { getMarkdownExtensionContributions } from './markdownExtensions';
|
|||
import { githubSlugifier } from './slugify';
|
||||
import { IMdWorkspace, VsCodeMdWorkspace } from './workspace';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
const contributions = getMarkdownExtensionContributions(context);
|
||||
context.subscriptions.push(contributions);
|
||||
|
||||
|
@ -25,11 +25,11 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
const workspace = new VsCodeMdWorkspace();
|
||||
context.subscriptions.push(workspace);
|
||||
|
||||
activateShared(context, workspace, engine, logger, contributions);
|
||||
startServer(context, workspace, engine);
|
||||
const client = await startServer(context, workspace, engine);
|
||||
activateShared(context, client, workspace, engine, logger, contributions);
|
||||
}
|
||||
|
||||
async function startServer(context: vscode.ExtensionContext, workspace: IMdWorkspace, parser: IMdParser): Promise<void> {
|
||||
function startServer(context: vscode.ExtensionContext, workspace: IMdWorkspace, parser: IMdParser): Promise<BaseLanguageClient> {
|
||||
const clientMain = vscode.extensions.getExtension('vscode.markdown-language-features')?.packageJSON?.main || '';
|
||||
|
||||
const serverMain = `./server/${clientMain.indexOf('/dist/') !== -1 ? 'dist' : 'out'}/node/main`;
|
||||
|
@ -44,7 +44,7 @@ async function startServer(context: vscode.ExtensionContext, workspace: IMdWorks
|
|||
run: { module: serverModule, transport: TransportKind.ipc },
|
||||
debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions }
|
||||
};
|
||||
await startClient((id, name, clientOptions) => {
|
||||
return startClient((id, name, clientOptions) => {
|
||||
return new LanguageClient(id, name, serverOptions, clientOptions);
|
||||
}, workspace, parser);
|
||||
}
|
||||
|
|
|
@ -1,27 +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 { ITextDocument } from '../types/textDocument';
|
||||
import { MdReferencesProvider } from './references';
|
||||
|
||||
export class MdVsCodeDefinitionProvider implements vscode.DefinitionProvider {
|
||||
|
||||
constructor(
|
||||
private readonly referencesProvider: MdReferencesProvider,
|
||||
) { }
|
||||
|
||||
async provideDefinition(document: ITextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.Definition | undefined> {
|
||||
const allRefs = await this.referencesProvider.getReferencesAtPosition(document, position, token);
|
||||
|
||||
return allRefs.find(ref => ref.kind === 'link' && ref.isDefinition)?.location;
|
||||
}
|
||||
}
|
||||
|
||||
export function registerDefinitionSupport(
|
||||
selector: vscode.DocumentSelector,
|
||||
referencesProvider: MdReferencesProvider,
|
||||
): vscode.Disposable {
|
||||
return vscode.languages.registerDefinitionProvider(selector, new MdVsCodeDefinitionProvider(referencesProvider));
|
||||
}
|
|
@ -4,21 +4,16 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as uri from 'vscode-uri';
|
||||
import { OpenDocumentLinkCommand } from '../commands/openDocumentLink';
|
||||
import { ILogger } from '../logging';
|
||||
import { IMdParser } from '../markdownEngine';
|
||||
import { getLine, ITextDocument } from '../types/textDocument';
|
||||
import { coalesce } from '../util/arrays';
|
||||
import { noopToken } from '../util/cancellation';
|
||||
import { Disposable } from '../util/dispose';
|
||||
import { Schemes } from '../util/schemes';
|
||||
import { MdDocumentInfoCache } from '../util/workspaceCache';
|
||||
import { IMdWorkspace } from '../workspace';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export interface ExternalHref {
|
||||
readonly kind: 'external';
|
||||
readonly uri: vscode.Uri;
|
||||
|
@ -543,62 +538,3 @@ export class LinkDefinitionSet implements Iterable<[string, MdLinkDefinition]> {
|
|||
return this._map.get(ref);
|
||||
}
|
||||
}
|
||||
|
||||
export class MdVsCodeLinkProvider implements vscode.DocumentLinkProvider {
|
||||
|
||||
constructor(
|
||||
private readonly _linkProvider: MdLinkProvider,
|
||||
) { }
|
||||
|
||||
public async provideDocumentLinks(
|
||||
document: ITextDocument,
|
||||
token: vscode.CancellationToken
|
||||
): Promise<vscode.DocumentLink[]> {
|
||||
const { links, definitions } = await this._linkProvider.getLinks(document);
|
||||
if (token.isCancellationRequested) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return coalesce(links.map(data => this.toValidDocumentLink(data, definitions)));
|
||||
}
|
||||
|
||||
private toValidDocumentLink(link: MdLink, definitionSet: LinkDefinitionSet): vscode.DocumentLink | undefined {
|
||||
switch (link.href.kind) {
|
||||
case 'external': {
|
||||
let target = link.href.uri;
|
||||
// Normalize VS Code links to target currently running version
|
||||
if (link.href.uri.scheme === Schemes.vscode || link.href.uri.scheme === Schemes['vscode-insiders']) {
|
||||
target = target.with({ scheme: vscode.env.uriScheme });
|
||||
}
|
||||
return new vscode.DocumentLink(link.source.hrefRange, link.href.uri);
|
||||
}
|
||||
case 'internal': {
|
||||
const uri = OpenDocumentLinkCommand.createCommandUri(link.source.resource, link.href.path, link.href.fragment);
|
||||
const documentLink = new vscode.DocumentLink(link.source.hrefRange, uri);
|
||||
documentLink.tooltip = localize('documentLink.tooltip', 'Follow link');
|
||||
return documentLink;
|
||||
}
|
||||
case 'reference': {
|
||||
// We only render reference links in the editor if they are actually defined.
|
||||
// This matches how reference links are rendered by markdown-it.
|
||||
const def = definitionSet.lookup(link.href.ref);
|
||||
if (def) {
|
||||
const documentLink = new vscode.DocumentLink(
|
||||
link.source.hrefRange,
|
||||
vscode.Uri.parse(`command:_markdown.moveCursorToPosition?${encodeURIComponent(JSON.stringify([def.source.hrefRange.start.line, def.source.hrefRange.start.character]))}`));
|
||||
documentLink.tooltip = localize('documentLink.referenceTooltip', 'Go to link definition');
|
||||
return documentLink;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function registerDocumentLinkSupport(
|
||||
selector: vscode.DocumentSelector,
|
||||
linkProvider: MdLinkProvider,
|
||||
): vscode.Disposable {
|
||||
return vscode.languages.registerDocumentLinkProvider(selector, new MdVsCodeLinkProvider(linkProvider));
|
||||
}
|
||||
|
|
|
@ -4,9 +4,10 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { BaseLanguageClient } from 'vscode-languageclient';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { Command, CommandManager } from '../commandManager';
|
||||
import { MdReferencesProvider } from './references';
|
||||
import { getReferencesToFileInWorkspace } from '../protocol';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
|
@ -16,7 +17,7 @@ export class FindFileReferencesCommand implements Command {
|
|||
public readonly id = 'markdown.findAllFileReferences';
|
||||
|
||||
constructor(
|
||||
private readonly referencesProvider: MdReferencesProvider,
|
||||
private readonly client: BaseLanguageClient,
|
||||
) { }
|
||||
|
||||
public async execute(resource?: vscode.Uri) {
|
||||
|
@ -33,8 +34,9 @@ export class FindFileReferencesCommand implements Command {
|
|||
location: vscode.ProgressLocation.Window,
|
||||
title: localize('progress.title', "Finding file references")
|
||||
}, async (_progress, token) => {
|
||||
const references = await this.referencesProvider.getReferencesToFileInWorkspace(resource!, token);
|
||||
const locations = references.map(ref => ref.location);
|
||||
const locations = (await this.client.sendRequest(getReferencesToFileInWorkspace, { uri: resource!.toString() }, token)).map(loc => {
|
||||
return new vscode.Location(vscode.Uri.parse(loc.uri), new vscode.Range(loc.range.start.line, loc.range.start.character, loc.range.end.line, loc.range.end.character));
|
||||
});
|
||||
|
||||
const config = vscode.workspace.getConfiguration('references');
|
||||
const existingSetting = config.inspect<string>('preferredLocation');
|
||||
|
@ -51,7 +53,7 @@ export class FindFileReferencesCommand implements Command {
|
|||
|
||||
export function registerFindFileReferenceSupport(
|
||||
commandManager: CommandManager,
|
||||
referencesProvider: MdReferencesProvider
|
||||
client: BaseLanguageClient,
|
||||
): vscode.Disposable {
|
||||
return commandManager.register(new FindFileReferencesCommand(referencesProvider));
|
||||
return commandManager.register(new FindFileReferencesCommand(client));
|
||||
}
|
||||
|
|
|
@ -1,369 +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 { dirname, resolve } from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import { IMdParser } from '../markdownEngine';
|
||||
import { TableOfContents } from '../tableOfContents';
|
||||
import { getLine, ITextDocument } from '../types/textDocument';
|
||||
import { resolveUriToMarkdownFile } from '../util/openDocumentLink';
|
||||
import { Schemes } from '../util/schemes';
|
||||
import { IMdWorkspace } from '../workspace';
|
||||
import { MdLinkProvider } from './documentLinks';
|
||||
|
||||
enum CompletionContextKind {
|
||||
/** `[...](|)` */
|
||||
Link,
|
||||
|
||||
/** `[...][|]` */
|
||||
ReferenceLink,
|
||||
|
||||
/** `[]: |` */
|
||||
LinkDefinition,
|
||||
}
|
||||
|
||||
interface AnchorContext {
|
||||
/**
|
||||
* Link text before the `#`.
|
||||
*
|
||||
* For `[text](xy#z|abc)` this is `xy`.
|
||||
*/
|
||||
readonly beforeAnchor: string;
|
||||
|
||||
/**
|
||||
* Text of the anchor before the current position.
|
||||
*
|
||||
* For `[text](xy#z|abc)` this is `z`.
|
||||
*/
|
||||
readonly anchorPrefix: string;
|
||||
}
|
||||
|
||||
interface CompletionContext {
|
||||
readonly kind: CompletionContextKind;
|
||||
|
||||
/**
|
||||
* Text of the link before the current position
|
||||
*
|
||||
* For `[text](xy#z|abc)` this is `xy#z`.
|
||||
*/
|
||||
readonly linkPrefix: string;
|
||||
|
||||
/**
|
||||
* Position of the start of the link.
|
||||
*
|
||||
* For `[text](xy#z|abc)` this is the position before `xy`.
|
||||
*/
|
||||
readonly linkTextStartPosition: vscode.Position;
|
||||
|
||||
/**
|
||||
* Text of the link after the current position.
|
||||
*
|
||||
* For `[text](xy#z|abc)` this is `abc`.
|
||||
*/
|
||||
readonly linkSuffix: string;
|
||||
|
||||
/**
|
||||
* Info if the link looks like it is for an anchor: `[](#header)`
|
||||
*/
|
||||
readonly anchorInfo?: AnchorContext;
|
||||
|
||||
/**
|
||||
* Indicates that the completion does not require encoding.
|
||||
*/
|
||||
readonly skipEncoding?: boolean;
|
||||
}
|
||||
|
||||
function tryDecodeUriComponent(str: string): string {
|
||||
try {
|
||||
return decodeURIComponent(str);
|
||||
} catch {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds path completions in markdown files by implementing {@link vscode.CompletionItemProvider}.
|
||||
*/
|
||||
export class MdVsCodePathCompletionProvider implements vscode.CompletionItemProvider {
|
||||
|
||||
constructor(
|
||||
private readonly workspace: IMdWorkspace,
|
||||
private readonly parser: IMdParser,
|
||||
private readonly linkProvider: MdLinkProvider,
|
||||
) { }
|
||||
|
||||
public async provideCompletionItems(document: ITextDocument, position: vscode.Position, _token: vscode.CancellationToken, _context: vscode.CompletionContext): Promise<vscode.CompletionItem[]> {
|
||||
if (!this.arePathSuggestionEnabled(document)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const context = this.getPathCompletionContext(document, position);
|
||||
if (!context) {
|
||||
return [];
|
||||
}
|
||||
|
||||
switch (context.kind) {
|
||||
case CompletionContextKind.ReferenceLink: {
|
||||
const items: vscode.CompletionItem[] = [];
|
||||
for await (const item of this.provideReferenceSuggestions(document, position, context)) {
|
||||
items.push(item);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
case CompletionContextKind.LinkDefinition:
|
||||
case CompletionContextKind.Link: {
|
||||
const items: vscode.CompletionItem[] = [];
|
||||
|
||||
const isAnchorInCurrentDoc = context.anchorInfo && context.anchorInfo.beforeAnchor.length === 0;
|
||||
|
||||
// Add anchor #links in current doc
|
||||
if (context.linkPrefix.length === 0 || isAnchorInCurrentDoc) {
|
||||
const insertRange = new vscode.Range(context.linkTextStartPosition, position);
|
||||
for await (const item of this.provideHeaderSuggestions(document, position, context, insertRange)) {
|
||||
items.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAnchorInCurrentDoc) {
|
||||
if (context.anchorInfo) { // Anchor to a different document
|
||||
const rawUri = this.resolveReference(document, context.anchorInfo.beforeAnchor);
|
||||
if (rawUri) {
|
||||
const otherDoc = await resolveUriToMarkdownFile(this.workspace, rawUri);
|
||||
if (otherDoc) {
|
||||
const anchorStartPosition = position.translate({ characterDelta: -(context.anchorInfo.anchorPrefix.length + 1) });
|
||||
const range = new vscode.Range(anchorStartPosition, position);
|
||||
for await (const item of this.provideHeaderSuggestions(otherDoc, position, context, range)) {
|
||||
items.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // Normal path suggestions
|
||||
for await (const item of this.providePathSuggestions(document, position, context)) {
|
||||
items.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private arePathSuggestionEnabled(document: ITextDocument): boolean {
|
||||
const config = vscode.workspace.getConfiguration('markdown', document.uri);
|
||||
return config.get('suggest.paths.enabled', true);
|
||||
}
|
||||
|
||||
/// [...](...|
|
||||
private readonly linkStartPattern = /\[([^\]]*?)\]\(\s*(<[^\>\)]*|[^\s\(\)]*)$/;
|
||||
|
||||
/// [...][...|
|
||||
private readonly referenceLinkStartPattern = /\[([^\]]*?)\]\[\s*([^\s\(\)]*)$/;
|
||||
|
||||
/// [id]: |
|
||||
private readonly definitionPattern = /^\s*\[[\w\-]+\]:\s*([^\s]*)$/m;
|
||||
|
||||
private getPathCompletionContext(document: ITextDocument, position: vscode.Position): CompletionContext | undefined {
|
||||
const line = getLine(document, position.line);
|
||||
|
||||
const linePrefixText = line.slice(0, position.character);
|
||||
const lineSuffixText = line.slice(position.character);
|
||||
|
||||
const linkPrefixMatch = linePrefixText.match(this.linkStartPattern);
|
||||
if (linkPrefixMatch) {
|
||||
const isAngleBracketLink = linkPrefixMatch[2].startsWith('<');
|
||||
const prefix = linkPrefixMatch[2].slice(isAngleBracketLink ? 1 : 0);
|
||||
if (this.refLooksLikeUrl(prefix)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const suffix = lineSuffixText.match(/^[^\)\s][^\)\s\>]*/);
|
||||
return {
|
||||
kind: CompletionContextKind.Link,
|
||||
linkPrefix: tryDecodeUriComponent(prefix),
|
||||
linkTextStartPosition: position.translate({ characterDelta: -prefix.length }),
|
||||
linkSuffix: suffix ? suffix[0] : '',
|
||||
anchorInfo: this.getAnchorContext(prefix),
|
||||
skipEncoding: isAngleBracketLink,
|
||||
};
|
||||
}
|
||||
|
||||
const definitionLinkPrefixMatch = linePrefixText.match(this.definitionPattern);
|
||||
if (definitionLinkPrefixMatch) {
|
||||
const isAngleBracketLink = definitionLinkPrefixMatch[1].startsWith('<');
|
||||
const prefix = definitionLinkPrefixMatch[1].slice(isAngleBracketLink ? 1 : 0);
|
||||
if (this.refLooksLikeUrl(prefix)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const suffix = lineSuffixText.match(/^[^\s]*/);
|
||||
return {
|
||||
kind: CompletionContextKind.LinkDefinition,
|
||||
linkPrefix: tryDecodeUriComponent(prefix),
|
||||
linkTextStartPosition: position.translate({ characterDelta: -prefix.length }),
|
||||
linkSuffix: suffix ? suffix[0] : '',
|
||||
anchorInfo: this.getAnchorContext(prefix),
|
||||
skipEncoding: isAngleBracketLink,
|
||||
};
|
||||
}
|
||||
|
||||
const referenceLinkPrefixMatch = linePrefixText.match(this.referenceLinkStartPattern);
|
||||
if (referenceLinkPrefixMatch) {
|
||||
const prefix = referenceLinkPrefixMatch[2];
|
||||
const suffix = lineSuffixText.match(/^[^\]\s]*/);
|
||||
return {
|
||||
kind: CompletionContextKind.ReferenceLink,
|
||||
linkPrefix: prefix,
|
||||
linkTextStartPosition: position.translate({ characterDelta: -prefix.length }),
|
||||
linkSuffix: suffix ? suffix[0] : '',
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if {@param ref} looks like a 'http:' style url.
|
||||
*/
|
||||
private refLooksLikeUrl(prefix: string): boolean {
|
||||
return /^\s*[\w\d\-]+:/.test(prefix);
|
||||
}
|
||||
|
||||
private getAnchorContext(prefix: string): AnchorContext | undefined {
|
||||
const anchorMatch = prefix.match(/^(.*)#([\w\d\-]*)$/);
|
||||
if (!anchorMatch) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
beforeAnchor: anchorMatch[1],
|
||||
anchorPrefix: anchorMatch[2],
|
||||
};
|
||||
}
|
||||
|
||||
private async *provideReferenceSuggestions(document: ITextDocument, position: vscode.Position, context: CompletionContext): AsyncIterable<vscode.CompletionItem> {
|
||||
const insertionRange = new vscode.Range(context.linkTextStartPosition, position);
|
||||
const replacementRange = new vscode.Range(insertionRange.start, position.translate({ characterDelta: context.linkSuffix.length }));
|
||||
|
||||
const { definitions } = await this.linkProvider.getLinks(document);
|
||||
for (const [_, def] of definitions) {
|
||||
yield {
|
||||
kind: vscode.CompletionItemKind.Reference,
|
||||
label: def.ref.text,
|
||||
range: {
|
||||
inserting: insertionRange,
|
||||
replacing: replacementRange,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async *provideHeaderSuggestions(document: ITextDocument, position: vscode.Position, context: CompletionContext, insertionRange: vscode.Range): AsyncIterable<vscode.CompletionItem> {
|
||||
const toc = await TableOfContents.createForDocumentOrNotebook(this.parser, document);
|
||||
for (const entry of toc.entries) {
|
||||
const replacementRange = new vscode.Range(insertionRange.start, position.translate({ characterDelta: context.linkSuffix.length }));
|
||||
yield {
|
||||
kind: vscode.CompletionItemKind.Reference,
|
||||
label: '#' + decodeURIComponent(entry.slug.value),
|
||||
range: {
|
||||
inserting: insertionRange,
|
||||
replacing: replacementRange,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async *providePathSuggestions(document: ITextDocument, position: vscode.Position, context: CompletionContext): AsyncIterable<vscode.CompletionItem> {
|
||||
const valueBeforeLastSlash = context.linkPrefix.substring(0, context.linkPrefix.lastIndexOf('/') + 1); // keep the last slash
|
||||
|
||||
const parentDir = this.resolveReference(document, valueBeforeLastSlash || '.');
|
||||
if (!parentDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pathSegmentStart = position.translate({ characterDelta: valueBeforeLastSlash.length - context.linkPrefix.length });
|
||||
const insertRange = new vscode.Range(pathSegmentStart, position);
|
||||
|
||||
const pathSegmentEnd = position.translate({ characterDelta: context.linkSuffix.length });
|
||||
const replacementRange = new vscode.Range(pathSegmentStart, pathSegmentEnd);
|
||||
|
||||
let dirInfo: [string, vscode.FileType][];
|
||||
try {
|
||||
dirInfo = await this.workspace.readDirectory(parentDir);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [name, type] of dirInfo) {
|
||||
// Exclude paths that start with `.`
|
||||
if (name.startsWith('.')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const isDir = type === vscode.FileType.Directory;
|
||||
yield {
|
||||
label: isDir ? name + '/' : name,
|
||||
insertText: (context.skipEncoding ? name : encodeURIComponent(name)) + (isDir ? '/' : ''),
|
||||
kind: isDir ? vscode.CompletionItemKind.Folder : vscode.CompletionItemKind.File,
|
||||
range: {
|
||||
inserting: insertRange,
|
||||
replacing: replacementRange,
|
||||
},
|
||||
command: isDir ? { command: 'editor.action.triggerSuggest', title: '' } : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private resolveReference(document: ITextDocument, ref: string): vscode.Uri | undefined {
|
||||
const docUri = this.getFileUriOfTextDocument(document);
|
||||
|
||||
if (ref.startsWith('/')) {
|
||||
const workspaceFolder = vscode.workspace.getWorkspaceFolder(docUri);
|
||||
if (workspaceFolder) {
|
||||
return vscode.Uri.joinPath(workspaceFolder.uri, ref);
|
||||
} else {
|
||||
return this.resolvePath(docUri, ref.slice(1));
|
||||
}
|
||||
}
|
||||
|
||||
return this.resolvePath(docUri, ref);
|
||||
}
|
||||
|
||||
private resolvePath(root: vscode.Uri, ref: string): vscode.Uri | undefined {
|
||||
try {
|
||||
if (root.scheme === Schemes.file) {
|
||||
return vscode.Uri.file(resolve(dirname(root.fsPath), ref));
|
||||
} else {
|
||||
return root.with({
|
||||
path: resolve(dirname(root.path), ref),
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private getFileUriOfTextDocument(document: ITextDocument) {
|
||||
if (document.uri.scheme === 'vscode-notebook-cell') {
|
||||
const notebook = vscode.workspace.notebookDocuments
|
||||
.find(notebook => notebook.getCells().some(cell => cell.document === document));
|
||||
|
||||
if (notebook) {
|
||||
return notebook.uri;
|
||||
}
|
||||
}
|
||||
|
||||
return document.uri;
|
||||
}
|
||||
}
|
||||
|
||||
export function registerPathCompletionSupport(
|
||||
selector: vscode.DocumentSelector,
|
||||
workspace: IMdWorkspace,
|
||||
parser: IMdParser,
|
||||
linkProvider: MdLinkProvider,
|
||||
): vscode.Disposable {
|
||||
return vscode.languages.registerCompletionItemProvider(selector, new MdVsCodePathCompletionProvider(workspace, parser, linkProvider), '.', '/', '#');
|
||||
}
|
|
@ -312,30 +312,6 @@ export class MdReferencesProvider extends Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@link vscode.ReferenceProvider} for markdown documents.
|
||||
*/
|
||||
export class MdVsCodeReferencesProvider implements vscode.ReferenceProvider {
|
||||
|
||||
public constructor(
|
||||
private readonly referencesProvider: MdReferencesProvider
|
||||
) { }
|
||||
|
||||
async provideReferences(document: ITextDocument, position: vscode.Position, context: vscode.ReferenceContext, token: vscode.CancellationToken): Promise<vscode.Location[]> {
|
||||
const allRefs = await this.referencesProvider.getReferencesAtPosition(document, position, token);
|
||||
return allRefs
|
||||
.filter(ref => context.includeDeclaration || !ref.isDefinition)
|
||||
.map(ref => ref.location);
|
||||
}
|
||||
}
|
||||
|
||||
export function registerReferencesSupport(
|
||||
selector: vscode.DocumentSelector,
|
||||
referencesProvider: MdReferencesProvider,
|
||||
): vscode.Disposable {
|
||||
return vscode.languages.registerReferenceProvider(selector, new MdVsCodeReferencesProvider(referencesProvider));
|
||||
}
|
||||
|
||||
export async function tryResolveLinkPath(originalUri: vscode.Uri, workspace: IMdWorkspace): Promise<vscode.Uri | undefined> {
|
||||
if (await workspace.pathExists(originalUri)) {
|
||||
return originalUri;
|
||||
|
|
|
@ -1,281 +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 path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as URI from 'vscode-uri';
|
||||
import { Slugifier } from '../slugify';
|
||||
import { ITextDocument } from '../types/textDocument';
|
||||
import { Disposable } from '../util/dispose';
|
||||
import { resolveDocumentLink } from '../util/openDocumentLink';
|
||||
import { IMdWorkspace } from '../workspace';
|
||||
import { InternalHref } from './documentLinks';
|
||||
import { MdHeaderReference, MdLinkReference, MdReference, MdReferencesProvider, tryResolveLinkPath } from './references';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
|
||||
export interface MdReferencesResponse {
|
||||
references: MdReference[];
|
||||
triggerRef: MdReference;
|
||||
}
|
||||
|
||||
interface MdFileRenameEdit {
|
||||
readonly from: vscode.Uri;
|
||||
readonly to: vscode.Uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type with additional metadata about the edits for testing
|
||||
*
|
||||
* This is needed since `vscode.WorkspaceEdit` does not expose info on file renames.
|
||||
*/
|
||||
export interface MdWorkspaceEdit {
|
||||
readonly edit: vscode.WorkspaceEdit;
|
||||
|
||||
readonly fileRenames?: ReadonlyArray<MdFileRenameEdit>;
|
||||
}
|
||||
|
||||
function tryDecodeUri(str: string): string {
|
||||
try {
|
||||
return decodeURI(str);
|
||||
} catch {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
export class MdVsCodeRenameProvider extends Disposable implements vscode.RenameProvider {
|
||||
|
||||
private cachedRefs?: {
|
||||
readonly resource: vscode.Uri;
|
||||
readonly version: number;
|
||||
readonly position: vscode.Position;
|
||||
readonly triggerRef: MdReference;
|
||||
readonly references: MdReference[];
|
||||
} | undefined;
|
||||
|
||||
private readonly renameNotSupportedText = localize('invalidRenameLocation', "Rename not supported at location");
|
||||
|
||||
public constructor(
|
||||
private readonly workspace: IMdWorkspace,
|
||||
private readonly referencesProvider: MdReferencesProvider,
|
||||
private readonly slugifier: Slugifier,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public async prepareRename(document: ITextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<undefined | { readonly range: vscode.Range; readonly placeholder: string }> {
|
||||
const allRefsInfo = await this.getAllReferences(document, position, token);
|
||||
if (token.isCancellationRequested) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!allRefsInfo || !allRefsInfo.references.length) {
|
||||
throw new Error(this.renameNotSupportedText);
|
||||
}
|
||||
|
||||
const triggerRef = allRefsInfo.triggerRef;
|
||||
switch (triggerRef.kind) {
|
||||
case 'header': {
|
||||
return { range: triggerRef.headerTextLocation.range, placeholder: triggerRef.headerText };
|
||||
}
|
||||
case 'link': {
|
||||
if (triggerRef.link.kind === 'definition') {
|
||||
// We may have been triggered on the ref or the definition itself
|
||||
if (triggerRef.link.ref.range.contains(position)) {
|
||||
return { range: triggerRef.link.ref.range, placeholder: triggerRef.link.ref.text };
|
||||
}
|
||||
}
|
||||
|
||||
if (triggerRef.link.href.kind === 'external') {
|
||||
return { range: triggerRef.link.source.hrefRange, placeholder: document.getText(triggerRef.link.source.hrefRange) };
|
||||
}
|
||||
|
||||
// See if we are renaming the fragment or the path
|
||||
const { fragmentRange } = triggerRef.link.source;
|
||||
if (fragmentRange?.contains(position)) {
|
||||
const declaration = this.findHeaderDeclaration(allRefsInfo.references);
|
||||
if (declaration) {
|
||||
return { range: fragmentRange, placeholder: declaration.headerText };
|
||||
}
|
||||
return { range: fragmentRange, placeholder: document.getText(fragmentRange) };
|
||||
}
|
||||
|
||||
const range = this.getFilePathRange(triggerRef);
|
||||
if (!range) {
|
||||
throw new Error(this.renameNotSupportedText);
|
||||
}
|
||||
return { range, placeholder: tryDecodeUri(document.getText(range)) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getFilePathRange(ref: MdLinkReference): vscode.Range {
|
||||
if (ref.link.source.fragmentRange) {
|
||||
return ref.link.source.hrefRange.with(undefined, ref.link.source.fragmentRange.start.translate(0, -1));
|
||||
}
|
||||
return ref.link.source.hrefRange;
|
||||
}
|
||||
|
||||
private findHeaderDeclaration(references: readonly MdReference[]): MdHeaderReference | undefined {
|
||||
return references.find(ref => ref.isDefinition && ref.kind === 'header') as MdHeaderReference | undefined;
|
||||
}
|
||||
|
||||
public async provideRenameEdits(document: ITextDocument, position: vscode.Position, newName: string, token: vscode.CancellationToken): Promise<vscode.WorkspaceEdit | undefined> {
|
||||
return (await this.provideRenameEditsImpl(document, position, newName, token))?.edit;
|
||||
}
|
||||
|
||||
public async provideRenameEditsImpl(document: ITextDocument, position: vscode.Position, newName: string, token: vscode.CancellationToken): Promise<MdWorkspaceEdit | undefined> {
|
||||
const allRefsInfo = await this.getAllReferences(document, position, token);
|
||||
if (token.isCancellationRequested || !allRefsInfo || !allRefsInfo.references.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const triggerRef = allRefsInfo.triggerRef;
|
||||
|
||||
if (triggerRef.kind === 'link' && (
|
||||
(triggerRef.link.kind === 'definition' && triggerRef.link.ref.range.contains(position)) || triggerRef.link.href.kind === 'reference'
|
||||
)) {
|
||||
return this.renameReferenceLinks(allRefsInfo, newName);
|
||||
} else if (triggerRef.kind === 'link' && triggerRef.link.href.kind === 'external') {
|
||||
return this.renameExternalLink(allRefsInfo, newName);
|
||||
} else if (triggerRef.kind === 'header' || (triggerRef.kind === 'link' && triggerRef.link.source.fragmentRange?.contains(position) && (triggerRef.link.kind === 'definition' || triggerRef.link.kind === 'link' && triggerRef.link.href.kind === 'internal'))) {
|
||||
return this.renameFragment(allRefsInfo, newName);
|
||||
} else if (triggerRef.kind === 'link' && !triggerRef.link.source.fragmentRange?.contains(position) && (triggerRef.link.kind === 'link' || triggerRef.link.kind === 'definition') && triggerRef.link.href.kind === 'internal') {
|
||||
return this.renameFilePath(triggerRef.link.source.resource, triggerRef.link.href, allRefsInfo, newName);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async renameFilePath(triggerDocument: vscode.Uri, triggerHref: InternalHref, allRefsInfo: MdReferencesResponse, newName: string): Promise<MdWorkspaceEdit> {
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
const fileRenames: MdFileRenameEdit[] = [];
|
||||
|
||||
const targetUri = await tryResolveLinkPath(triggerHref.path, this.workspace) ?? triggerHref.path;
|
||||
|
||||
const rawNewFilePath = resolveDocumentLink(newName, triggerDocument);
|
||||
let resolvedNewFilePath = rawNewFilePath;
|
||||
if (!URI.Utils.extname(resolvedNewFilePath)) {
|
||||
// If the newly entered path doesn't have a file extension but the original file did
|
||||
// tack on a .md file extension
|
||||
if (URI.Utils.extname(targetUri)) {
|
||||
resolvedNewFilePath = resolvedNewFilePath.with({
|
||||
path: resolvedNewFilePath.path + '.md'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// First rename the file
|
||||
if (await this.workspace.pathExists(targetUri)) {
|
||||
fileRenames.push({ from: targetUri, to: resolvedNewFilePath });
|
||||
edit.renameFile(targetUri, resolvedNewFilePath);
|
||||
}
|
||||
|
||||
// Then update all refs to it
|
||||
for (const ref of allRefsInfo.references) {
|
||||
if (ref.kind === 'link') {
|
||||
// Try to preserve style of existing links
|
||||
let newPath: string;
|
||||
if (ref.link.source.hrefText.startsWith('/')) {
|
||||
const root = resolveDocumentLink('/', ref.link.source.resource);
|
||||
newPath = '/' + path.relative(root.toString(true), rawNewFilePath.toString(true));
|
||||
} else {
|
||||
const rootDir = URI.Utils.dirname(ref.link.source.resource);
|
||||
if (rootDir.scheme === rawNewFilePath.scheme && rootDir.scheme !== 'untitled') {
|
||||
newPath = path.relative(rootDir.toString(true), rawNewFilePath.toString(true));
|
||||
if (newName.startsWith('./') && !newPath.startsWith('../') || newName.startsWith('.\\') && !newPath.startsWith('..\\')) {
|
||||
newPath = './' + newPath;
|
||||
}
|
||||
} else {
|
||||
newPath = newName;
|
||||
}
|
||||
}
|
||||
edit.replace(ref.link.source.resource, this.getFilePathRange(ref), encodeURI(newPath.replace(/\\/g, '/')));
|
||||
}
|
||||
}
|
||||
|
||||
return { edit, fileRenames };
|
||||
}
|
||||
|
||||
private renameFragment(allRefsInfo: MdReferencesResponse, newName: string): MdWorkspaceEdit {
|
||||
const slug = this.slugifier.fromHeading(newName).value;
|
||||
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
for (const ref of allRefsInfo.references) {
|
||||
switch (ref.kind) {
|
||||
case 'header':
|
||||
edit.replace(ref.location.uri, ref.headerTextLocation.range, newName);
|
||||
break;
|
||||
|
||||
case 'link':
|
||||
edit.replace(ref.link.source.resource, ref.link.source.fragmentRange ?? ref.location.range, !ref.link.source.fragmentRange || ref.link.href.kind === 'external' ? newName : slug);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { edit };
|
||||
}
|
||||
|
||||
private renameExternalLink(allRefsInfo: MdReferencesResponse, newName: string): MdWorkspaceEdit {
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
for (const ref of allRefsInfo.references) {
|
||||
if (ref.kind === 'link') {
|
||||
edit.replace(ref.link.source.resource, ref.location.range, newName);
|
||||
}
|
||||
}
|
||||
return { edit };
|
||||
}
|
||||
|
||||
private renameReferenceLinks(allRefsInfo: MdReferencesResponse, newName: string): MdWorkspaceEdit {
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
for (const ref of allRefsInfo.references) {
|
||||
if (ref.kind === 'link') {
|
||||
if (ref.link.kind === 'definition') {
|
||||
edit.replace(ref.link.source.resource, ref.link.ref.range, newName);
|
||||
} else {
|
||||
edit.replace(ref.link.source.resource, ref.link.source.fragmentRange ?? ref.location.range, newName);
|
||||
}
|
||||
}
|
||||
}
|
||||
return { edit };
|
||||
}
|
||||
|
||||
private async getAllReferences(document: ITextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<MdReferencesResponse | undefined> {
|
||||
const version = document.version;
|
||||
|
||||
if (this.cachedRefs
|
||||
&& this.cachedRefs.resource.fsPath === document.uri.fsPath
|
||||
&& this.cachedRefs.version === document.version
|
||||
&& this.cachedRefs.position.isEqual(position)
|
||||
) {
|
||||
return this.cachedRefs;
|
||||
}
|
||||
|
||||
const references = await this.referencesProvider.getReferencesAtPosition(document, position, token);
|
||||
const triggerRef = references.find(ref => ref.isTriggerLocation);
|
||||
if (!triggerRef) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
this.cachedRefs = {
|
||||
resource: document.uri,
|
||||
version,
|
||||
position,
|
||||
references,
|
||||
triggerRef
|
||||
};
|
||||
return this.cachedRefs;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function registerRenameSupport(
|
||||
selector: vscode.DocumentSelector,
|
||||
workspace: IMdWorkspace,
|
||||
referencesProvider: MdReferencesProvider,
|
||||
slugifier: Slugifier,
|
||||
): vscode.Disposable {
|
||||
return vscode.languages.registerRenameProvider(selector, new MdVsCodeRenameProvider(workspace, referencesProvider, slugifier));
|
||||
}
|
18
extensions/markdown-language-features/src/protocol.ts
Normal file
18
extensions/markdown-language-features/src/protocol.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import Token = require('markdown-it/lib/token');
|
||||
import { RequestType } from 'vscode-languageclient';
|
||||
import type * as lsp from 'vscode-languageserver-types';
|
||||
|
||||
// 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');
|
||||
|
||||
// To server
|
||||
export const getReferencesToFileInWorkspace = new RequestType<{ uri: string }, lsp.Location[], any>('markdown/getReferencesToFileInWorkspace');
|
|
@ -1,144 +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 { MdVsCodeDefinitionProvider } from '../languageFeatures/definitions';
|
||||
import { MdReferencesProvider } from '../languageFeatures/references';
|
||||
import { MdTableOfContentsProvider } from '../tableOfContents';
|
||||
import { noopToken } from '../util/cancellation';
|
||||
import { DisposableStore } from '../util/dispose';
|
||||
import { InMemoryDocument } from '../util/inMemoryDocument';
|
||||
import { IMdWorkspace } from '../workspace';
|
||||
import { createNewMarkdownEngine } from './engine';
|
||||
import { InMemoryMdWorkspace } from './inMemoryWorkspace';
|
||||
import { nulLogger } from './nulLogging';
|
||||
import { joinLines, withStore, workspacePath } from './util';
|
||||
|
||||
|
||||
function getDefinition(store: DisposableStore, doc: InMemoryDocument, pos: vscode.Position, workspace: IMdWorkspace) {
|
||||
const engine = createNewMarkdownEngine();
|
||||
const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger));
|
||||
const referencesProvider = store.add(new MdReferencesProvider(engine, workspace, tocProvider, nulLogger));
|
||||
const provider = new MdVsCodeDefinitionProvider(referencesProvider);
|
||||
return provider.provideDefinition(doc, pos, noopToken);
|
||||
}
|
||||
|
||||
function assertDefinitionsEqual(actualDef: vscode.Definition, ...expectedDefs: { uri: vscode.Uri; line: number; startCharacter?: number; endCharacter?: number }[]) {
|
||||
const actualDefsArr = Array.isArray(actualDef) ? actualDef : [actualDef];
|
||||
|
||||
assert.strictEqual(actualDefsArr.length, expectedDefs.length, `Definition counts should match`);
|
||||
|
||||
for (let i = 0; i < actualDefsArr.length; ++i) {
|
||||
const actual = actualDefsArr[i];
|
||||
const expected = expectedDefs[i];
|
||||
assert.strictEqual(actual.uri.toString(), expected.uri.toString(), `Definition '${i}' has expected document`);
|
||||
assert.strictEqual(actual.range.start.line, expected.line, `Definition '${i}' has expected start line`);
|
||||
assert.strictEqual(actual.range.end.line, expected.line, `Definition '${i}' has expected end line`);
|
||||
if (typeof expected.startCharacter !== 'undefined') {
|
||||
assert.strictEqual(actual.range.start.character, expected.startCharacter, `Definition '${i}' has expected start character`);
|
||||
}
|
||||
if (typeof expected.endCharacter !== 'undefined') {
|
||||
assert.strictEqual(actual.range.end.character, expected.endCharacter, `Definition '${i}' has expected end character`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suite('markdown: Go to definition', () => {
|
||||
test('Should not return definition when on link text', withStore(async (store) => {
|
||||
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
|
||||
`[ref](#abc)`,
|
||||
`[ref]: http://example.com`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const defs = await getDefinition(store, doc, new vscode.Position(0, 1), workspace);
|
||||
assert.deepStrictEqual(defs, undefined);
|
||||
}));
|
||||
|
||||
test('Should find definition links within file from link', withStore(async (store) => {
|
||||
const docUri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(docUri, joinLines(
|
||||
`[link 1][abc]`, // trigger here
|
||||
``,
|
||||
`[abc]: https://example.com`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const defs = await getDefinition(store, doc, new vscode.Position(0, 12), workspace);
|
||||
assertDefinitionsEqual(defs!,
|
||||
{ uri: docUri, line: 2 },
|
||||
);
|
||||
}));
|
||||
|
||||
test('Should find definition links using shorthand', withStore(async (store) => {
|
||||
const docUri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(docUri, joinLines(
|
||||
`[ref]`, // trigger 1
|
||||
``,
|
||||
`[yes][ref]`, // trigger 2
|
||||
``,
|
||||
`[ref]: /Hello.md` // trigger 3
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
{
|
||||
const defs = await getDefinition(store, doc, new vscode.Position(0, 2), workspace);
|
||||
assertDefinitionsEqual(defs!,
|
||||
{ uri: docUri, line: 4 },
|
||||
);
|
||||
}
|
||||
{
|
||||
const defs = await getDefinition(store, doc, new vscode.Position(2, 7), workspace);
|
||||
assertDefinitionsEqual(defs!,
|
||||
{ uri: docUri, line: 4 },
|
||||
);
|
||||
}
|
||||
{
|
||||
const defs = await getDefinition(store, doc, new vscode.Position(4, 2), workspace);
|
||||
assertDefinitionsEqual(defs!,
|
||||
{ uri: docUri, line: 4 },
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
test('Should find definition links within file from definition', withStore(async (store) => {
|
||||
const docUri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(docUri, joinLines(
|
||||
`[link 1][abc]`,
|
||||
``,
|
||||
`[abc]: https://example.com`, // trigger here
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const defs = await getDefinition(store, doc, new vscode.Position(2, 3), workspace);
|
||||
assertDefinitionsEqual(defs!,
|
||||
{ uri: docUri, line: 2 },
|
||||
);
|
||||
}));
|
||||
|
||||
test('Should not find definition links across files', withStore(async (store) => {
|
||||
const docUri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(docUri, joinLines(
|
||||
`[link 1][abc]`,
|
||||
``,
|
||||
`[abc]: https://example.com`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([
|
||||
doc,
|
||||
new InMemoryDocument(workspacePath('other.md'), joinLines(
|
||||
`[link 1][abc]`,
|
||||
``,
|
||||
`[abc]: https://example.com?bad`
|
||||
))
|
||||
]));
|
||||
|
||||
const defs = await getDefinition(store, doc, new vscode.Position(0, 12), workspace);
|
||||
assertDefinitionsEqual(defs!,
|
||||
{ uri: docUri, line: 2 },
|
||||
);
|
||||
}));
|
||||
});
|
|
@ -24,7 +24,7 @@ function workspaceFile(...segments: string[]) {
|
|||
|
||||
async function getLinksForFile(file: vscode.Uri): Promise<vscode.DocumentLink[]> {
|
||||
debugLog('getting links', file.toString(), Date.now());
|
||||
const r = (await vscode.commands.executeCommand<vscode.DocumentLink[]>('vscode.executeLinkProvider', file))!;
|
||||
const r = (await vscode.commands.executeCommand<vscode.DocumentLink[]>('vscode.executeLinkProvider', file, /*linkResolveCount*/ 100))!;
|
||||
debugLog('got links', file.toString(), Date.now());
|
||||
return r;
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ async function getLinksForFile(file: vscode.Uri): Promise<vscode.DocumentLink[]>
|
|||
}
|
||||
});
|
||||
|
||||
test('Should navigate to fragment within current untitled file', async () => {
|
||||
test('Should navigate to fragment within current untitled file', async () => { // TODO: skip for now for ls migration
|
||||
const testFile = workspaceFile('x.md').with({ scheme: 'untitled' });
|
||||
await withFileContents(testFile, joinLines(
|
||||
'[](#second)',
|
||||
|
@ -171,7 +171,7 @@ async function withFileContents(file: vscode.Uri, contents: string): Promise<voi
|
|||
async function executeLink(link: vscode.DocumentLink) {
|
||||
debugLog('executeingLink', link.target?.toString(), Date.now());
|
||||
|
||||
const args = JSON.parse(decodeURIComponent(link.target!.query));
|
||||
await vscode.commands.executeCommand(link.target!.path, args);
|
||||
const args: any[] = JSON.parse(decodeURIComponent(link.target!.query));
|
||||
await vscode.commands.executeCommand(link.target!.path, vscode.Uri.from(args[0]), ...args.slice(1));
|
||||
debugLog('executedLink', vscode.window.activeTextEditor?.document.toString(), Date.now());
|
||||
}
|
||||
|
|
|
@ -1,539 +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 { MdLink, MdLinkComputer, MdLinkProvider, MdVsCodeLinkProvider } from '../languageFeatures/documentLinks';
|
||||
import { noopToken } from '../util/cancellation';
|
||||
import { InMemoryDocument } from '../util/inMemoryDocument';
|
||||
import { createNewMarkdownEngine } from './engine';
|
||||
import { InMemoryMdWorkspace } from './inMemoryWorkspace';
|
||||
import { nulLogger } from './nulLogging';
|
||||
import { assertRangeEqual, joinLines, workspacePath } from './util';
|
||||
|
||||
|
||||
suite('Markdown: MdLinkComputer', () => {
|
||||
|
||||
function getLinksForFile(fileContents: string): Promise<MdLink[]> {
|
||||
const doc = new InMemoryDocument(workspacePath('x.md'), fileContents);
|
||||
const engine = createNewMarkdownEngine();
|
||||
const linkProvider = new MdLinkComputer(engine);
|
||||
return linkProvider.getAllLinks(doc, noopToken);
|
||||
}
|
||||
|
||||
function assertLinksEqual(actualLinks: readonly MdLink[], expected: ReadonlyArray<vscode.Range | { readonly range: vscode.Range; readonly sourceText: string }>) {
|
||||
assert.strictEqual(actualLinks.length, expected.length);
|
||||
|
||||
for (let i = 0; i < actualLinks.length; ++i) {
|
||||
const exp = expected[i];
|
||||
if ('range' in exp) {
|
||||
assertRangeEqual(actualLinks[i].source.hrefRange, exp.range, `Range ${i} to be equal`);
|
||||
assert.strictEqual(actualLinks[i].source.hrefText, exp.sourceText, `Source text ${i} to be equal`);
|
||||
} else {
|
||||
assertRangeEqual(actualLinks[i].source.hrefRange, exp, `Range ${i} to be equal`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test('Should not return anything for empty document', async () => {
|
||||
const links = await getLinksForFile('');
|
||||
assertLinksEqual(links, []);
|
||||
});
|
||||
|
||||
test('Should not return anything for simple document without links', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
'# a',
|
||||
'fdasfdfsafsa',
|
||||
));
|
||||
assertLinksEqual(links, []);
|
||||
});
|
||||
|
||||
test('Should detect basic http links', async () => {
|
||||
const links = await getLinksForFile('a [b](https://example.com) c');
|
||||
assertLinksEqual(links, [
|
||||
new vscode.Range(0, 6, 0, 25)
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should detect basic workspace links', async () => {
|
||||
{
|
||||
const links = await getLinksForFile('a [b](./file) c');
|
||||
assertLinksEqual(links, [
|
||||
new vscode.Range(0, 6, 0, 12)
|
||||
]);
|
||||
}
|
||||
{
|
||||
const links = await getLinksForFile('a [b](file.png) c');
|
||||
assertLinksEqual(links, [
|
||||
new vscode.Range(0, 6, 0, 14)
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
test('Should detect links with title', async () => {
|
||||
const links = await getLinksForFile('a [b](https://example.com "abc") c');
|
||||
assertLinksEqual(links, [
|
||||
new vscode.Range(0, 6, 0, 25)
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should handle links with escaped characters in name (#35245)', async () => {
|
||||
const links = await getLinksForFile('a [b\\]](./file)');
|
||||
assertLinksEqual(links, [
|
||||
new vscode.Range(0, 8, 0, 14)
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should handle links with balanced parens', async () => {
|
||||
{
|
||||
const links = await getLinksForFile('a [b](https://example.com/a()c) c');
|
||||
assertLinksEqual(links, [
|
||||
new vscode.Range(0, 6, 0, 30)
|
||||
]);
|
||||
}
|
||||
{
|
||||
const links = await getLinksForFile('a [b](https://example.com/a(b)c) c');
|
||||
assertLinksEqual(links, [
|
||||
new vscode.Range(0, 6, 0, 31)
|
||||
]);
|
||||
}
|
||||
{
|
||||
// #49011
|
||||
const links = await getLinksForFile('[A link](http://ThisUrlhasParens/A_link(in_parens))');
|
||||
assertLinksEqual(links, [
|
||||
new vscode.Range(0, 9, 0, 50)
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
test('Should ignore bracketed text inside link title (#150921)', async () => {
|
||||
{
|
||||
const links = await getLinksForFile('[some [inner] in title](link)');
|
||||
assertLinksEqual(links, [
|
||||
new vscode.Range(0, 24, 0, 28),
|
||||
]);
|
||||
}
|
||||
{
|
||||
const links = await getLinksForFile('[some [inner] in title](<link>)');
|
||||
assertLinksEqual(links, [
|
||||
new vscode.Range(0, 25, 0, 29),
|
||||
]);
|
||||
}
|
||||
{
|
||||
const links = await getLinksForFile('[some [inner with space] in title](link)');
|
||||
assertLinksEqual(links, [
|
||||
new vscode.Range(0, 35, 0, 39),
|
||||
]);
|
||||
}
|
||||
{
|
||||
const links = await getLinksForFile(joinLines(
|
||||
`# h`,
|
||||
`[[a]](http://example.com)`,
|
||||
));
|
||||
assertLinksEqual(links, [
|
||||
new vscode.Range(1, 6, 1, 24),
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
test('Should handle two links without space', async () => {
|
||||
const links = await getLinksForFile('a ([test](test)[test2](test2)) c');
|
||||
assertLinksEqual(links, [
|
||||
new vscode.Range(0, 10, 0, 14),
|
||||
new vscode.Range(0, 23, 0, 28)
|
||||
]);
|
||||
});
|
||||
|
||||
test('should handle hyperlinked images (#49238)', async () => {
|
||||
{
|
||||
const links = await getLinksForFile('[![alt text](image.jpg)](https://example.com)');
|
||||
assertLinksEqual(links, [
|
||||
new vscode.Range(0, 25, 0, 44),
|
||||
new vscode.Range(0, 13, 0, 22),
|
||||
]);
|
||||
}
|
||||
{
|
||||
const links = await getLinksForFile('[![a]( whitespace.jpg )]( https://whitespace.com )');
|
||||
assertLinksEqual(links, [
|
||||
new vscode.Range(0, 26, 0, 48),
|
||||
new vscode.Range(0, 7, 0, 21),
|
||||
]);
|
||||
}
|
||||
{
|
||||
const links = await getLinksForFile('[![a](img1.jpg)](file1.txt) text [![a](img2.jpg)](file2.txt)');
|
||||
assertLinksEqual(links, [
|
||||
new vscode.Range(0, 17, 0, 26),
|
||||
new vscode.Range(0, 6, 0, 14),
|
||||
new vscode.Range(0, 50, 0, 59),
|
||||
new vscode.Range(0, 39, 0, 47),
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
test('Should not consider link references starting with ^ character valid (#107471)', async () => {
|
||||
const links = await getLinksForFile('[^reference]: https://example.com');
|
||||
assertLinksEqual(links, []);
|
||||
});
|
||||
|
||||
test('Should find definitions links with spaces in angle brackets (#136073)', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
'[a]: <b c>',
|
||||
'[b]: <cd>',
|
||||
));
|
||||
|
||||
assertLinksEqual(links, [
|
||||
{ range: new vscode.Range(0, 6, 0, 9), sourceText: 'b c' },
|
||||
{ range: new vscode.Range(1, 6, 1, 8), sourceText: 'cd' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should only find one link for reference sources [a]: source (#141285)', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
'[Works]: https://example.com',
|
||||
));
|
||||
|
||||
assertLinksEqual(links, [
|
||||
{ range: new vscode.Range(0, 9, 0, 28), sourceText: 'https://example.com' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should find reference link shorthand (#141285)', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
'[ref]',
|
||||
'[ref]: https://example.com',
|
||||
));
|
||||
assertLinksEqual(links, [
|
||||
{ range: new vscode.Range(0, 1, 0, 4), sourceText: 'ref' },
|
||||
{ range: new vscode.Range(1, 7, 1, 26), sourceText: 'https://example.com' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should find reference link shorthand using empty closing brackets (#141285)', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
'[ref][]',
|
||||
));
|
||||
assertLinksEqual(links, [
|
||||
new vscode.Range(0, 1, 0, 4),
|
||||
]);
|
||||
});
|
||||
|
||||
test.skip('Should find reference link shorthand for link with space in label (#141285)', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
'[ref with space]',
|
||||
));
|
||||
assertLinksEqual(links, [
|
||||
new vscode.Range(0, 7, 0, 26),
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should not include reference links with escaped leading brackets', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
`\\[bad link][good]`,
|
||||
`\\[good]`,
|
||||
`[good]: http://example.com`,
|
||||
));
|
||||
assertLinksEqual(links, [
|
||||
new vscode.Range(2, 8, 2, 26) // Should only find the definition
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should not consider links in code fenced with backticks', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
'```',
|
||||
'[b](https://example.com)',
|
||||
'```'));
|
||||
assertLinksEqual(links, []);
|
||||
});
|
||||
|
||||
test('Should not consider links in code fenced with tilde', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
'~~~',
|
||||
'[b](https://example.com)',
|
||||
'~~~'));
|
||||
assertLinksEqual(links, []);
|
||||
});
|
||||
|
||||
test('Should not consider links in indented code', async () => {
|
||||
const links = await getLinksForFile(' [b](https://example.com)');
|
||||
assertLinksEqual(links, []);
|
||||
});
|
||||
|
||||
test('Should not consider links in inline code span', async () => {
|
||||
const links = await getLinksForFile('`[b](https://example.com)`');
|
||||
assertLinksEqual(links, []);
|
||||
});
|
||||
|
||||
test('Should not consider links with code span inside', async () => {
|
||||
const links = await getLinksForFile('[li`nk](https://example.com`)');
|
||||
assertLinksEqual(links, []);
|
||||
});
|
||||
|
||||
test('Should not consider links in multiline inline code span', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
'`` ',
|
||||
'[b](https://example.com)',
|
||||
'``'));
|
||||
assertLinksEqual(links, []);
|
||||
});
|
||||
|
||||
test('Should not consider link references in code fenced with backticks (#146714)', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
'```',
|
||||
'[a] [bb]',
|
||||
'```'));
|
||||
assertLinksEqual(links, []);
|
||||
});
|
||||
|
||||
test('Should not consider reference sources in code fenced with backticks (#146714)', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
'```',
|
||||
'[a]: http://example.com;',
|
||||
'[b]: <http://example.com>;',
|
||||
'[c]: (http://example.com);',
|
||||
'```'));
|
||||
assertLinksEqual(links, []);
|
||||
});
|
||||
|
||||
test('Should not consider links in multiline inline code span between between text', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
'[b](https://1.com) `[b](https://2.com)',
|
||||
'[b](https://3.com) ` [b](https://4.com)'));
|
||||
|
||||
assertLinksEqual(links, [
|
||||
new vscode.Range(0, 4, 0, 17),
|
||||
new vscode.Range(1, 25, 1, 38),
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should not consider links in multiline inline code span with new line after the first backtick', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
'`',
|
||||
'[b](https://example.com)`'));
|
||||
assertLinksEqual(links, []);
|
||||
});
|
||||
|
||||
test('Should not miss links in invalid multiline inline code span', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
'`` ',
|
||||
'',
|
||||
'[b](https://example.com)',
|
||||
'',
|
||||
'``'));
|
||||
assertLinksEqual(links, [
|
||||
new vscode.Range(2, 4, 2, 23)
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should find autolinks', async () => {
|
||||
const links = await getLinksForFile('pre <http://example.com> post');
|
||||
assertLinksEqual(links, [
|
||||
new vscode.Range(0, 5, 0, 23)
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should not detect links inside html comment blocks', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
`<!-- <http://example.com> -->`,
|
||||
`<!-- [text](./foo.md) -->`,
|
||||
`<!-- [text]: ./foo.md -->`,
|
||||
``,
|
||||
`<!--`,
|
||||
`<http://example.com>`,
|
||||
`-->`,
|
||||
``,
|
||||
`<!--`,
|
||||
`[text](./foo.md)`,
|
||||
`-->`,
|
||||
``,
|
||||
`<!--`,
|
||||
`[text]: ./foo.md`,
|
||||
`-->`,
|
||||
));
|
||||
assertLinksEqual(links, []);
|
||||
});
|
||||
|
||||
test.skip('Should not detect links inside inline html comments', async () => {
|
||||
// See #149678
|
||||
const links = await getLinksForFile(joinLines(
|
||||
`text <!-- <http://example.com> --> text`,
|
||||
`text <!-- [text](./foo.md) --> text`,
|
||||
`text <!-- [text]: ./foo.md --> text`,
|
||||
``,
|
||||
`text <!--`,
|
||||
`<http://example.com>`,
|
||||
`--> text`,
|
||||
``,
|
||||
`text <!--`,
|
||||
`[text](./foo.md)`,
|
||||
`--> text`,
|
||||
``,
|
||||
`text <!--`,
|
||||
`[text]: ./foo.md`,
|
||||
`--> text`,
|
||||
));
|
||||
assertLinksEqual(links, []);
|
||||
});
|
||||
|
||||
test('Should not mark checkboxes as links', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
'- [x]',
|
||||
'- [X]',
|
||||
'- [ ]',
|
||||
'* [x]',
|
||||
'* [X]',
|
||||
'* [ ]',
|
||||
``,
|
||||
`[x]: http://example.com`
|
||||
));
|
||||
assertLinksEqual(links, [
|
||||
new vscode.Range(7, 5, 7, 23)
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should still find links on line with checkbox', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
'- [x] [x]',
|
||||
'- [X] [x]',
|
||||
'- [] [x]',
|
||||
``,
|
||||
`[x]: http://example.com`
|
||||
));
|
||||
|
||||
assertLinksEqual(links, [
|
||||
new vscode.Range(0, 7, 0, 8),
|
||||
new vscode.Range(1, 7, 1, 8),
|
||||
new vscode.Range(2, 6, 2, 7),
|
||||
new vscode.Range(4, 5, 4, 23),
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should find link only within angle brackets.', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
`[link](<path>)`
|
||||
));
|
||||
assertLinksEqual(links, [new vscode.Range(0, 8, 0, 12)]);
|
||||
});
|
||||
|
||||
test('Should find link within angle brackets even with link title.', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
`[link](<path> "test title")`
|
||||
));
|
||||
assertLinksEqual(links, [new vscode.Range(0, 8, 0, 12)]);
|
||||
});
|
||||
|
||||
test('Should find link within angle brackets even with surrounding spaces.', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
`[link]( <path> )`
|
||||
));
|
||||
assertLinksEqual(links, [new vscode.Range(0, 9, 0, 13)]);
|
||||
});
|
||||
|
||||
test('Should find link within angle brackets for image hyperlinks.', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
`![link](<path>)`
|
||||
));
|
||||
assertLinksEqual(links, [new vscode.Range(0, 9, 0, 13)]);
|
||||
});
|
||||
|
||||
test('Should find link with spaces in angle brackets for image hyperlinks with titles.', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
`![link](< path > "test")`
|
||||
));
|
||||
assertLinksEqual(links, [new vscode.Range(0, 9, 0, 15)]);
|
||||
});
|
||||
|
||||
|
||||
test('Should not find link due to incorrect angle bracket notation or usage.', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
`[link](<path )`,
|
||||
`[link](<> path>)`,
|
||||
`[link](> path)`,
|
||||
));
|
||||
assertLinksEqual(links, []);
|
||||
});
|
||||
|
||||
test('Should find link within angle brackets even with space inside link.', async () => {
|
||||
|
||||
const links = await getLinksForFile(joinLines(
|
||||
`[link](<pa th>)`
|
||||
));
|
||||
|
||||
assertLinksEqual(links, [new vscode.Range(0, 8, 0, 13)]);
|
||||
});
|
||||
|
||||
test('Should find links with titles', async () => {
|
||||
const links = await getLinksForFile(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))`,
|
||||
));
|
||||
assertLinksEqual(links, [
|
||||
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 not include link with empty angle bracket', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
`[](<>)`,
|
||||
`[link](<>)`,
|
||||
`[link](<> "text")`,
|
||||
`[link](<> 'text')`,
|
||||
`[link](<> (text))`,
|
||||
));
|
||||
assertLinksEqual(links, []);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
suite('Markdown: VS Code DocumentLinkProvider', () => {
|
||||
|
||||
function getLinksForFile(fileContents: string) {
|
||||
const doc = new InMemoryDocument(workspacePath('x.md'), fileContents);
|
||||
const workspace = new InMemoryMdWorkspace([doc]);
|
||||
|
||||
const engine = createNewMarkdownEngine();
|
||||
const linkProvider = new MdLinkProvider(engine, workspace, nulLogger);
|
||||
const provider = new MdVsCodeLinkProvider(linkProvider);
|
||||
return provider.provideDocumentLinks(doc, noopToken);
|
||||
}
|
||||
|
||||
function assertLinksEqual(actualLinks: readonly vscode.DocumentLink[], expectedRanges: readonly vscode.Range[]) {
|
||||
assert.strictEqual(actualLinks.length, expectedRanges.length);
|
||||
|
||||
for (let i = 0; i < actualLinks.length; ++i) {
|
||||
assertRangeEqual(actualLinks[i].range, expectedRanges[i], `Range ${i} to be equal`);
|
||||
}
|
||||
}
|
||||
|
||||
test('Should include defined reference links (#141285)', async () => {
|
||||
const links = await getLinksForFile(joinLines(
|
||||
'[ref]',
|
||||
'[ref][]',
|
||||
'[ref][ref]',
|
||||
'',
|
||||
'[ref]: http://example.com'
|
||||
));
|
||||
assertLinksEqual(links, [
|
||||
new vscode.Range(0, 1, 0, 4),
|
||||
new vscode.Range(1, 1, 1, 4),
|
||||
new vscode.Range(2, 6, 2, 9),
|
||||
new vscode.Range(4, 7, 4, 25),
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should not include reference link shorthand when definition does not exist (#141285)', async () => {
|
||||
const links = await getLinksForFile('[ref]');
|
||||
assertLinksEqual(links, []);
|
||||
});
|
||||
});
|
|
@ -1,120 +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 { MdReference, MdReferencesProvider } from '../languageFeatures/references';
|
||||
import { MdTableOfContentsProvider } from '../tableOfContents';
|
||||
import { noopToken } from '../util/cancellation';
|
||||
import { DisposableStore } from '../util/dispose';
|
||||
import { InMemoryDocument } from '../util/inMemoryDocument';
|
||||
import { IMdWorkspace } from '../workspace';
|
||||
import { createNewMarkdownEngine } from './engine';
|
||||
import { InMemoryMdWorkspace } from './inMemoryWorkspace';
|
||||
import { nulLogger } from './nulLogging';
|
||||
import { joinLines, withStore, workspacePath } from './util';
|
||||
|
||||
|
||||
function getFileReferences(store: DisposableStore, resource: vscode.Uri, workspace: IMdWorkspace) {
|
||||
const engine = createNewMarkdownEngine();
|
||||
const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger));
|
||||
const computer = store.add(new MdReferencesProvider(engine, workspace, tocProvider, nulLogger));
|
||||
return computer.getReferencesToFileInWorkspace(resource, noopToken);
|
||||
}
|
||||
|
||||
function assertReferencesEqual(actualRefs: readonly MdReference[], ...expectedRefs: { uri: vscode.Uri; line: number }[]) {
|
||||
assert.strictEqual(actualRefs.length, expectedRefs.length, `Reference counts should match`);
|
||||
|
||||
for (let i = 0; i < actualRefs.length; ++i) {
|
||||
const actual = actualRefs[i].location;
|
||||
const expected = expectedRefs[i];
|
||||
assert.strictEqual(actual.uri.toString(), expected.uri.toString(), `Ref '${i}' has expected document`);
|
||||
assert.strictEqual(actual.range.start.line, expected.line, `Ref '${i}' has expected start line`);
|
||||
assert.strictEqual(actual.range.end.line, expected.line, `Ref '${i}' has expected end line`);
|
||||
}
|
||||
}
|
||||
|
||||
suite('markdown: find file references', () => {
|
||||
|
||||
test('Should find basic references', withStore(async (store) => {
|
||||
const docUri = workspacePath('doc.md');
|
||||
const otherUri = workspacePath('other.md');
|
||||
const workspace = store.add(new InMemoryMdWorkspace([
|
||||
new InMemoryDocument(docUri, joinLines(
|
||||
`# header`,
|
||||
`[link 1](./other.md)`,
|
||||
`[link 2](./other.md)`
|
||||
)),
|
||||
new InMemoryDocument(otherUri, joinLines(
|
||||
`# header`,
|
||||
`pre`,
|
||||
`[link 3](./other.md)`,
|
||||
`post`
|
||||
)),
|
||||
]));
|
||||
|
||||
const refs = await getFileReferences(store, otherUri, workspace);
|
||||
assertReferencesEqual(refs,
|
||||
{ uri: docUri, line: 1 },
|
||||
{ uri: docUri, line: 2 },
|
||||
{ uri: otherUri, line: 2 },
|
||||
);
|
||||
}));
|
||||
|
||||
test('Should find references with and without file extensions', withStore(async (store) => {
|
||||
const docUri = workspacePath('doc.md');
|
||||
const otherUri = workspacePath('other.md');
|
||||
const workspace = store.add(new InMemoryMdWorkspace([
|
||||
new InMemoryDocument(docUri, joinLines(
|
||||
`# header`,
|
||||
`[link 1](./other.md)`,
|
||||
`[link 2](./other)`
|
||||
)),
|
||||
new InMemoryDocument(otherUri, joinLines(
|
||||
`# header`,
|
||||
`pre`,
|
||||
`[link 3](./other.md)`,
|
||||
`[link 4](./other)`,
|
||||
`post`
|
||||
)),
|
||||
]));
|
||||
|
||||
const refs = await getFileReferences(store, otherUri, workspace);
|
||||
assertReferencesEqual(refs,
|
||||
{ uri: docUri, line: 1 },
|
||||
{ uri: docUri, line: 2 },
|
||||
{ uri: otherUri, line: 2 },
|
||||
{ uri: otherUri, line: 3 },
|
||||
);
|
||||
}));
|
||||
|
||||
test('Should find references with headers on links', withStore(async (store) => {
|
||||
const docUri = workspacePath('doc.md');
|
||||
const otherUri = workspacePath('other.md');
|
||||
const workspace = store.add(new InMemoryMdWorkspace([
|
||||
new InMemoryDocument(docUri, joinLines(
|
||||
`# header`,
|
||||
`[link 1](./other.md#sub-bla)`,
|
||||
`[link 2](./other#sub-bla)`
|
||||
)),
|
||||
new InMemoryDocument(otherUri, joinLines(
|
||||
`# header`,
|
||||
`pre`,
|
||||
`[link 3](./other.md#sub-bla)`,
|
||||
`[link 4](./other#sub-bla)`,
|
||||
`post`
|
||||
)),
|
||||
]));
|
||||
|
||||
const refs = await getFileReferences(store, otherUri, workspace);
|
||||
assertReferencesEqual(refs,
|
||||
{ uri: docUri, line: 1 },
|
||||
{ uri: docUri, line: 2 },
|
||||
{ uri: otherUri, line: 2 },
|
||||
{ uri: otherUri, line: 3 },
|
||||
);
|
||||
}));
|
||||
});
|
|
@ -1,313 +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 { MdLinkProvider } from '../languageFeatures/documentLinks';
|
||||
import { MdVsCodePathCompletionProvider } from '../languageFeatures/pathCompletions';
|
||||
import { noopToken } from '../util/cancellation';
|
||||
import { InMemoryDocument } from '../util/inMemoryDocument';
|
||||
import { IMdWorkspace } from '../workspace';
|
||||
import { createNewMarkdownEngine } from './engine';
|
||||
import { InMemoryMdWorkspace } from './inMemoryWorkspace';
|
||||
import { nulLogger } from './nulLogging';
|
||||
import { CURSOR, getCursorPositions, joinLines, workspacePath } from './util';
|
||||
|
||||
|
||||
async function getCompletionsAtCursor(resource: vscode.Uri, fileContents: string, workspace?: IMdWorkspace) {
|
||||
const doc = new InMemoryDocument(resource, fileContents);
|
||||
|
||||
const engine = createNewMarkdownEngine();
|
||||
const ws = workspace ?? new InMemoryMdWorkspace([doc]);
|
||||
const linkProvider = new MdLinkProvider(engine, ws, nulLogger);
|
||||
const provider = new MdVsCodePathCompletionProvider(ws, engine, linkProvider);
|
||||
const cursorPositions = getCursorPositions(fileContents, doc);
|
||||
const completions = await provider.provideCompletionItems(doc, cursorPositions[0], noopToken, {
|
||||
triggerCharacter: undefined,
|
||||
triggerKind: vscode.CompletionTriggerKind.Invoke,
|
||||
});
|
||||
|
||||
return completions.sort((a, b) => (a.label as string).localeCompare(b.label as string));
|
||||
}
|
||||
|
||||
function assertCompletionsEqual(actual: readonly vscode.CompletionItem[], expected: readonly { label: string; insertText?: string }[]) {
|
||||
assert.strictEqual(actual.length, expected.length, 'Completion counts should be equal');
|
||||
|
||||
for (let i = 0; i < actual.length; ++i) {
|
||||
assert.strictEqual(actual[i].label, expected[i].label, `Completion labels ${i} should be equal`);
|
||||
if (typeof expected[i].insertText !== 'undefined') {
|
||||
assert.strictEqual(actual[i].insertText, expected[i].insertText, `Completion insert texts ${i} should be equal`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suite('Markdown: Path completions', () => {
|
||||
|
||||
test('Should not return anything when triggered in empty doc', async () => {
|
||||
const completions = await getCompletionsAtCursor(workspacePath('new.md'), `${CURSOR}`);
|
||||
assertCompletionsEqual(completions, []);
|
||||
});
|
||||
|
||||
test('Should return anchor completions', async () => {
|
||||
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
|
||||
`[](#${CURSOR}`,
|
||||
``,
|
||||
`# A b C`,
|
||||
`# x y Z`,
|
||||
));
|
||||
|
||||
assertCompletionsEqual(completions, [
|
||||
{ label: '#a-b-c' },
|
||||
{ label: '#x-y-z' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should not return suggestions for http links', async () => {
|
||||
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
|
||||
`[](http:${CURSOR}`,
|
||||
``,
|
||||
`# http`,
|
||||
`# http:`,
|
||||
`# https:`,
|
||||
));
|
||||
|
||||
assertCompletionsEqual(completions, []);
|
||||
});
|
||||
|
||||
test('Should return relative path suggestions', async () => {
|
||||
const workspace = new InMemoryMdWorkspace([
|
||||
new InMemoryDocument(workspacePath('a.md'), ''),
|
||||
new InMemoryDocument(workspacePath('b.md'), ''),
|
||||
new InMemoryDocument(workspacePath('sub/foo.md'), ''),
|
||||
]);
|
||||
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
|
||||
`[](${CURSOR}`,
|
||||
``,
|
||||
`# A b C`,
|
||||
), workspace);
|
||||
|
||||
assertCompletionsEqual(completions, [
|
||||
{ label: '#a-b-c' },
|
||||
{ label: 'a.md' },
|
||||
{ label: 'b.md' },
|
||||
{ label: 'sub/' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should return relative path suggestions using ./', async () => {
|
||||
const workspace = new InMemoryMdWorkspace([
|
||||
new InMemoryDocument(workspacePath('a.md'), ''),
|
||||
new InMemoryDocument(workspacePath('b.md'), ''),
|
||||
new InMemoryDocument(workspacePath('sub/foo.md'), ''),
|
||||
]);
|
||||
|
||||
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
|
||||
`[](./${CURSOR}`,
|
||||
``,
|
||||
`# A b C`,
|
||||
), workspace);
|
||||
|
||||
assertCompletionsEqual(completions, [
|
||||
{ label: 'a.md' },
|
||||
{ label: 'b.md' },
|
||||
{ label: 'sub/' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should return absolute path suggestions using /', async () => {
|
||||
const workspace = new InMemoryMdWorkspace([
|
||||
new InMemoryDocument(workspacePath('a.md'), ''),
|
||||
new InMemoryDocument(workspacePath('b.md'), ''),
|
||||
new InMemoryDocument(workspacePath('sub/c.md'), ''),
|
||||
]);
|
||||
|
||||
const completions = await getCompletionsAtCursor(workspacePath('sub', 'new.md'), joinLines(
|
||||
`[](/${CURSOR}`,
|
||||
``,
|
||||
`# A b C`,
|
||||
), workspace);
|
||||
|
||||
assertCompletionsEqual(completions, [
|
||||
{ label: 'a.md' },
|
||||
{ label: 'b.md' },
|
||||
{ label: 'sub/' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should return anchor suggestions in other file', async () => {
|
||||
const workspace = new InMemoryMdWorkspace([
|
||||
new InMemoryDocument(workspacePath('b.md'), joinLines(
|
||||
`# b`,
|
||||
``,
|
||||
`[./a](./a)`,
|
||||
``,
|
||||
`# header1`,
|
||||
)),
|
||||
]);
|
||||
const completions = await getCompletionsAtCursor(workspacePath('sub', 'new.md'), joinLines(
|
||||
`[](/b.md#${CURSOR}`,
|
||||
), workspace);
|
||||
|
||||
assertCompletionsEqual(completions, [
|
||||
{ label: '#b' },
|
||||
{ label: '#header1' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should reference links for current file', async () => {
|
||||
const completions = await getCompletionsAtCursor(workspacePath('sub', 'new.md'), joinLines(
|
||||
`[][${CURSOR}`,
|
||||
``,
|
||||
`[ref-1]: bla`,
|
||||
`[ref-2]: bla`,
|
||||
));
|
||||
|
||||
assertCompletionsEqual(completions, [
|
||||
{ label: 'ref-1' },
|
||||
{ label: 'ref-2' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should complete headers in link definitions', async () => {
|
||||
const completions = await getCompletionsAtCursor(workspacePath('sub', 'new.md'), joinLines(
|
||||
`# a B c`,
|
||||
`# x y Z`,
|
||||
`[ref-1]: ${CURSOR}`,
|
||||
));
|
||||
|
||||
assertCompletionsEqual(completions, [
|
||||
{ label: '#a-b-c' },
|
||||
{ label: '#x-y-z' },
|
||||
{ label: 'new.md' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should complete relative paths in link definitions', async () => {
|
||||
const workspace = new InMemoryMdWorkspace([
|
||||
new InMemoryDocument(workspacePath('a.md'), ''),
|
||||
new InMemoryDocument(workspacePath('b.md'), ''),
|
||||
new InMemoryDocument(workspacePath('sub/c.md'), ''),
|
||||
]);
|
||||
|
||||
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
|
||||
`# a B c`,
|
||||
`[ref-1]: ${CURSOR}`,
|
||||
), workspace);
|
||||
|
||||
assertCompletionsEqual(completions, [
|
||||
{ label: '#a-b-c' },
|
||||
{ label: 'a.md' },
|
||||
{ label: 'b.md' },
|
||||
{ label: 'sub/' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should escape spaces in path names', async () => {
|
||||
const workspace = new InMemoryMdWorkspace([
|
||||
new InMemoryDocument(workspacePath('a.md'), ''),
|
||||
new InMemoryDocument(workspacePath('b.md'), ''),
|
||||
new InMemoryDocument(workspacePath('sub/file with space.md'), ''),
|
||||
]);
|
||||
|
||||
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
|
||||
`[](./sub/${CURSOR})`
|
||||
), workspace);
|
||||
|
||||
assertCompletionsEqual(completions, [
|
||||
{ label: 'file with space.md', insertText: 'file%20with%20space.md' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should support completions on angle bracket path with spaces', async () => {
|
||||
const workspace = new InMemoryMdWorkspace([
|
||||
new InMemoryDocument(workspacePath('sub with space/a.md'), ''),
|
||||
new InMemoryDocument(workspacePath('b.md'), ''),
|
||||
]);
|
||||
|
||||
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
|
||||
`[](</sub with space/${CURSOR}`
|
||||
), workspace);
|
||||
|
||||
assertCompletionsEqual(completions, [
|
||||
{ label: 'a.md', insertText: 'a.md' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should not escape spaces in path names that use angle brackets', async () => {
|
||||
const workspace = new InMemoryMdWorkspace([
|
||||
new InMemoryDocument(workspacePath('sub/file with space.md'), ''),
|
||||
]);
|
||||
|
||||
{
|
||||
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
|
||||
`[](<./sub/${CURSOR}`
|
||||
), workspace);
|
||||
|
||||
assertCompletionsEqual(completions, [
|
||||
{ label: 'file with space.md', insertText: 'file with space.md' },
|
||||
]);
|
||||
}
|
||||
{
|
||||
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
|
||||
`[](<./sub/${CURSOR}>`
|
||||
), workspace);
|
||||
|
||||
assertCompletionsEqual(completions, [
|
||||
{ label: 'file with space.md', insertText: 'file with space.md' },
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
test('Should complete paths for path with encoded spaces', async () => {
|
||||
const workspace = new InMemoryMdWorkspace([
|
||||
new InMemoryDocument(workspacePath('a.md'), ''),
|
||||
new InMemoryDocument(workspacePath('b.md'), ''),
|
||||
new InMemoryDocument(workspacePath('sub with space/file.md'), ''),
|
||||
]);
|
||||
|
||||
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
|
||||
`[](./sub%20with%20space/${CURSOR})`
|
||||
), workspace);
|
||||
|
||||
assertCompletionsEqual(completions, [
|
||||
{ label: 'file.md', insertText: 'file.md' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should complete definition path for path with encoded spaces', async () => {
|
||||
const workspace = new InMemoryMdWorkspace([
|
||||
new InMemoryDocument(workspacePath('a.md'), ''),
|
||||
new InMemoryDocument(workspacePath('b.md'), ''),
|
||||
new InMemoryDocument(workspacePath('sub with space/file.md'), ''),
|
||||
]);
|
||||
|
||||
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
|
||||
`[def]: ./sub%20with%20space/${CURSOR}`
|
||||
), workspace);
|
||||
|
||||
assertCompletionsEqual(completions, [
|
||||
{ label: 'file.md', insertText: 'file.md' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should support definition path with angle brackets', async () => {
|
||||
const workspace = new InMemoryMdWorkspace([
|
||||
new InMemoryDocument(workspacePath('a.md'), ''),
|
||||
new InMemoryDocument(workspacePath('b.md'), ''),
|
||||
new InMemoryDocument(workspacePath('sub with space/file.md'), ''),
|
||||
]);
|
||||
|
||||
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
|
||||
`[def]: <./${CURSOR}>`
|
||||
), workspace);
|
||||
|
||||
assertCompletionsEqual(completions, [
|
||||
{ label: 'a.md', insertText: 'a.md' },
|
||||
{ label: 'b.md', insertText: 'b.md' },
|
||||
{ label: 'sub with space/', insertText: 'sub with space/' },
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -1,635 +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 { MdReferencesProvider, MdVsCodeReferencesProvider } from '../languageFeatures/references';
|
||||
import { MdTableOfContentsProvider } from '../tableOfContents';
|
||||
import { noopToken } from '../util/cancellation';
|
||||
import { DisposableStore } from '../util/dispose';
|
||||
import { InMemoryDocument } from '../util/inMemoryDocument';
|
||||
import { IMdWorkspace } from '../workspace';
|
||||
import { createNewMarkdownEngine } from './engine';
|
||||
import { InMemoryMdWorkspace } from './inMemoryWorkspace';
|
||||
import { nulLogger } from './nulLogging';
|
||||
import { joinLines, withStore, workspacePath } from './util';
|
||||
|
||||
|
||||
async function getReferences(store: DisposableStore, doc: InMemoryDocument, pos: vscode.Position, workspace: IMdWorkspace) {
|
||||
const engine = createNewMarkdownEngine();
|
||||
const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger));
|
||||
const computer = store.add(new MdReferencesProvider(engine, workspace, tocProvider, nulLogger));
|
||||
const provider = new MdVsCodeReferencesProvider(computer);
|
||||
const refs = await provider.provideReferences(doc, pos, { includeDeclaration: true }, noopToken);
|
||||
return refs.sort((a, b) => {
|
||||
const pathCompare = a.uri.toString().localeCompare(b.uri.toString());
|
||||
if (pathCompare !== 0) {
|
||||
return pathCompare;
|
||||
}
|
||||
return a.range.start.compareTo(b.range.start);
|
||||
});
|
||||
}
|
||||
|
||||
function assertReferencesEqual(actualRefs: readonly vscode.Location[], ...expectedRefs: { uri: vscode.Uri; line: number; startCharacter?: number; endCharacter?: number }[]) {
|
||||
assert.strictEqual(actualRefs.length, expectedRefs.length, `Reference counts should match`);
|
||||
|
||||
for (let i = 0; i < actualRefs.length; ++i) {
|
||||
const actual = actualRefs[i];
|
||||
const expected = expectedRefs[i];
|
||||
assert.strictEqual(actual.uri.toString(), expected.uri.toString(), `Ref '${i}' has expected document`);
|
||||
assert.strictEqual(actual.range.start.line, expected.line, `Ref '${i}' has expected start line`);
|
||||
assert.strictEqual(actual.range.end.line, expected.line, `Ref '${i}' has expected end line`);
|
||||
if (typeof expected.startCharacter !== 'undefined') {
|
||||
assert.strictEqual(actual.range.start.character, expected.startCharacter, `Ref '${i}' has expected start character`);
|
||||
}
|
||||
if (typeof expected.endCharacter !== 'undefined') {
|
||||
assert.strictEqual(actual.range.end.character, expected.endCharacter, `Ref '${i}' has expected end character`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suite('Markdown: Find all references', () => {
|
||||
test('Should not return references when not on header or link', withStore(async (store) => {
|
||||
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
|
||||
`# abc`,
|
||||
``,
|
||||
`[link 1](#abc)`,
|
||||
`text`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
{
|
||||
const refs = await getReferences(store, doc, new vscode.Position(1, 0), workspace);
|
||||
assert.deepStrictEqual(refs, []);
|
||||
}
|
||||
{
|
||||
const refs = await getReferences(store, doc, new vscode.Position(3, 2), workspace);
|
||||
assert.deepStrictEqual(refs, []);
|
||||
}
|
||||
}));
|
||||
|
||||
test('Should find references from header within same file', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`# abc`,
|
||||
``,
|
||||
`[link 1](#abc)`,
|
||||
`[not link](#noabc)`,
|
||||
`[link 2](#abc)`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const refs = await getReferences(store, doc, new vscode.Position(0, 3), workspace);
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri, line: 0 },
|
||||
{ uri, line: 2 },
|
||||
{ uri, line: 4 },
|
||||
);
|
||||
}));
|
||||
|
||||
test('Should not return references when on link text', withStore(async (store) => {
|
||||
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
|
||||
`[ref](#abc)`,
|
||||
`[ref]: http://example.com`,
|
||||
));
|
||||
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const refs = await getReferences(store, doc, new vscode.Position(0, 1), workspace);
|
||||
assert.deepStrictEqual(refs, []);
|
||||
}));
|
||||
|
||||
test('Should find references using normalized slug', withStore(async (store) => {
|
||||
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
|
||||
`# a B c`,
|
||||
`[simple](#a-b-c)`,
|
||||
`[start underscore](#_a-b-c)`,
|
||||
`[different case](#a-B-C)`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
{
|
||||
// Trigger header
|
||||
|
||||
const refs = await getReferences(store, doc, new vscode.Position(0, 0), workspace);
|
||||
assert.deepStrictEqual(refs!.length, 4);
|
||||
}
|
||||
{
|
||||
// Trigger on line 1
|
||||
const refs = await getReferences(store, doc, new vscode.Position(1, 12), workspace);
|
||||
assert.deepStrictEqual(refs!.length, 4);
|
||||
}
|
||||
{
|
||||
// Trigger on line 2
|
||||
const refs = await getReferences(store, doc, new vscode.Position(2, 24), workspace);
|
||||
assert.deepStrictEqual(refs!.length, 4);
|
||||
}
|
||||
{
|
||||
// Trigger on line 3
|
||||
const refs = await getReferences(store, doc, new vscode.Position(3, 20), workspace);
|
||||
assert.deepStrictEqual(refs!.length, 4);
|
||||
}
|
||||
}));
|
||||
|
||||
test('Should find references from header across files', withStore(async (store) => {
|
||||
const docUri = workspacePath('doc.md');
|
||||
const other1Uri = workspacePath('sub', 'other.md');
|
||||
const other2Uri = workspacePath('zOther2.md');
|
||||
|
||||
const doc = new InMemoryDocument(docUri, joinLines(
|
||||
`# abc`,
|
||||
``,
|
||||
`[link 1](#abc)`,
|
||||
));
|
||||
|
||||
const workspace = store.add(new InMemoryMdWorkspace([
|
||||
doc,
|
||||
new InMemoryDocument(other1Uri, joinLines(
|
||||
`[not link](#abc)`,
|
||||
`[not link](/doc.md#abz)`,
|
||||
`[link](/doc.md#abc)`
|
||||
)),
|
||||
new InMemoryDocument(other2Uri, joinLines(
|
||||
`[not link](#abc)`,
|
||||
`[not link](./doc.md#abz)`,
|
||||
`[link](./doc.md#abc)`
|
||||
))
|
||||
]));
|
||||
|
||||
const refs = await getReferences(store, doc, new vscode.Position(0, 3), workspace);
|
||||
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri: docUri, line: 0 }, // Header definition
|
||||
{ uri: docUri, line: 2 },
|
||||
{ uri: other1Uri, line: 2 },
|
||||
{ uri: other2Uri, line: 2 },
|
||||
);
|
||||
}));
|
||||
|
||||
test('Should find references from header to link definitions ', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`# abc`,
|
||||
``,
|
||||
`[bla]: #abc`
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const refs = await getReferences(store, doc, new vscode.Position(0, 3), workspace);
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri, line: 0 }, // Header definition
|
||||
{ uri, line: 2 },
|
||||
);
|
||||
}));
|
||||
|
||||
test('Should find header references from link definition', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`# A b C`,
|
||||
`[text][bla]`,
|
||||
`[bla]: #a-b-c`, // trigger here
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const refs = await getReferences(store, doc, new vscode.Position(2, 9), workspace);
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri, line: 0 }, // Header definition
|
||||
{ uri, line: 2 },
|
||||
);
|
||||
}));
|
||||
|
||||
test('Should find references from link within same file', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`# abc`,
|
||||
``,
|
||||
`[link 1](#abc)`,
|
||||
`[not link](#noabc)`,
|
||||
`[link 2](#abc)`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const refs = await getReferences(store, doc, new vscode.Position(2, 10), workspace);
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri, line: 0 }, // Header definition
|
||||
{ uri, line: 2 },
|
||||
{ uri, line: 4 },
|
||||
);
|
||||
}));
|
||||
|
||||
test('Should find references from link across files', withStore(async (store) => {
|
||||
const docUri = workspacePath('doc.md');
|
||||
const other1Uri = workspacePath('sub', 'other.md');
|
||||
const other2Uri = workspacePath('zOther2.md');
|
||||
|
||||
const doc = new InMemoryDocument(docUri, joinLines(
|
||||
`# abc`,
|
||||
``,
|
||||
`[link 1](#abc)`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([
|
||||
doc,
|
||||
new InMemoryDocument(other1Uri, joinLines(
|
||||
`[not link](#abc)`,
|
||||
`[not link](/doc.md#abz)`,
|
||||
`[with ext](/doc.md#abc)`,
|
||||
`[without ext](/doc#abc)`
|
||||
)),
|
||||
new InMemoryDocument(other2Uri, joinLines(
|
||||
`[not link](#abc)`,
|
||||
`[not link](./doc.md#abz)`,
|
||||
`[link](./doc.md#abc)`
|
||||
))
|
||||
]));
|
||||
|
||||
const refs = await getReferences(store, doc, new vscode.Position(2, 10), workspace);
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri: docUri, line: 0 }, // Header definition
|
||||
{ uri: docUri, line: 2 },
|
||||
{ uri: other1Uri, line: 2 }, // Other with ext
|
||||
{ uri: other1Uri, line: 3 }, // Other without ext
|
||||
{ uri: other2Uri, line: 2 }, // Other2
|
||||
);
|
||||
}));
|
||||
|
||||
test('Should find references without requiring file extensions', withStore(async (store) => {
|
||||
const docUri = workspacePath('doc.md');
|
||||
const other1Uri = workspacePath('other.md');
|
||||
|
||||
const doc = new InMemoryDocument(docUri, joinLines(
|
||||
`# a B c`,
|
||||
``,
|
||||
`[link 1](#a-b-c)`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([
|
||||
doc,
|
||||
new InMemoryDocument(other1Uri, joinLines(
|
||||
`[not link](#a-b-c)`,
|
||||
`[not link](/doc.md#a-b-z)`,
|
||||
`[with ext](/doc.md#a-b-c)`,
|
||||
`[without ext](/doc#a-b-c)`,
|
||||
`[rel with ext](./doc.md#a-b-c)`,
|
||||
`[rel without ext](./doc#a-b-c)`
|
||||
)),
|
||||
]));
|
||||
|
||||
const refs = await getReferences(store, doc, new vscode.Position(2, 10), workspace);
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri: docUri, line: 0 }, // Header definition
|
||||
{ uri: docUri, line: 2 },
|
||||
{ uri: other1Uri, line: 2 }, // Other with ext
|
||||
{ uri: other1Uri, line: 3 }, // Other without ext
|
||||
{ uri: other1Uri, line: 4 }, // Other relative link with ext
|
||||
{ uri: other1Uri, line: 5 }, // Other relative link without ext
|
||||
);
|
||||
}));
|
||||
|
||||
test('Should find references from link across files when triggered on link without file extension', withStore(async (store) => {
|
||||
const docUri = workspacePath('doc.md');
|
||||
const other1Uri = workspacePath('sub', 'other.md');
|
||||
|
||||
const doc = new InMemoryDocument(docUri, joinLines(
|
||||
`[with ext](./sub/other#header)`,
|
||||
`[without ext](./sub/other.md#header)`,
|
||||
));
|
||||
|
||||
const workspace = store.add(new InMemoryMdWorkspace([
|
||||
doc,
|
||||
new InMemoryDocument(other1Uri, joinLines(
|
||||
`pre`,
|
||||
`# header`,
|
||||
`post`
|
||||
)),
|
||||
]));
|
||||
|
||||
const refs = await getReferences(store, doc, new vscode.Position(0, 23), workspace);
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri: docUri, line: 0 },
|
||||
{ uri: docUri, line: 1 },
|
||||
{ uri: other1Uri, line: 1 }, // Header definition
|
||||
);
|
||||
}));
|
||||
|
||||
test('Should include header references when triggered on file link', withStore(async (store) => {
|
||||
const docUri = workspacePath('doc.md');
|
||||
const otherUri = workspacePath('sub', 'other.md');
|
||||
|
||||
const doc = new InMemoryDocument(docUri, joinLines(
|
||||
`[with ext](./sub/other)`,
|
||||
`[with ext](./sub/other#header)`,
|
||||
`[without ext](./sub/other.md#no-such-header)`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([
|
||||
doc,
|
||||
new InMemoryDocument(otherUri, joinLines(
|
||||
`pre`,
|
||||
`# header`,
|
||||
`post`
|
||||
)),
|
||||
]));
|
||||
|
||||
const refs = await getReferences(store, doc, new vscode.Position(0, 15), workspace);
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri: docUri, line: 0 },
|
||||
{ uri: docUri, line: 1 },
|
||||
{ uri: docUri, line: 2 },
|
||||
);
|
||||
}));
|
||||
|
||||
test('Should not include refs from other file to own header', withStore(async (store) => {
|
||||
const docUri = workspacePath('doc.md');
|
||||
const otherUri = workspacePath('sub', 'other.md');
|
||||
|
||||
const doc = new InMemoryDocument(docUri, joinLines(
|
||||
`[other](./sub/other)`, // trigger here
|
||||
));
|
||||
|
||||
const workspace = store.add(new InMemoryMdWorkspace([
|
||||
doc,
|
||||
new InMemoryDocument(otherUri, joinLines(
|
||||
`# header`, // Definition should not be included since we triggered on a file link
|
||||
`[text](#header)`, // Ref should not be included since it is to own file
|
||||
)),
|
||||
]));
|
||||
|
||||
const refs = await getReferences(store, doc, new vscode.Position(0, 15), workspace);
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri: docUri, line: 0 },
|
||||
);
|
||||
}));
|
||||
|
||||
test('Should find explicit references to own file ', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`[bare](doc.md)`, // trigger here
|
||||
`[rel](./doc.md)`,
|
||||
`[abs](/doc.md)`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const refs = await getReferences(store, doc, new vscode.Position(0, 12), workspace);
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri, line: 0 },
|
||||
{ uri, line: 1 },
|
||||
{ uri, line: 2 },
|
||||
);
|
||||
}));
|
||||
|
||||
test('Should support finding references to http uri', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`[1](http://example.com)`,
|
||||
`[no](https://example.com)`,
|
||||
`[2](http://example.com)`,
|
||||
`[3]: http://example.com`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const refs = await getReferences(store, doc, new vscode.Position(0, 13), workspace);
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri, line: 0 },
|
||||
{ uri, line: 2 },
|
||||
{ uri, line: 3 },
|
||||
);
|
||||
}));
|
||||
|
||||
test('Should consider authority, scheme and paths when finding references to http uri', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`[1](http://example.com/cat)`,
|
||||
`[2](http://example.com)`,
|
||||
`[3](http://example.com/dog)`,
|
||||
`[4](http://example.com/cat/looong)`,
|
||||
`[5](http://example.com/cat)`,
|
||||
`[6](http://other.com/cat)`,
|
||||
`[7](https://example.com/cat)`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const refs = await getReferences(store, doc, new vscode.Position(0, 13), workspace);
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri, line: 0 },
|
||||
{ uri, line: 4 },
|
||||
);
|
||||
}));
|
||||
|
||||
test('Should support finding references to http uri across files', withStore(async (store) => {
|
||||
const uri1 = workspacePath('doc.md');
|
||||
const uri2 = workspacePath('doc2.md');
|
||||
const doc = new InMemoryDocument(uri1, joinLines(
|
||||
`[1](http://example.com)`,
|
||||
`[3]: http://example.com`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([
|
||||
doc,
|
||||
new InMemoryDocument(uri2, joinLines(
|
||||
`[other](http://example.com)`
|
||||
))
|
||||
]));
|
||||
|
||||
const refs = await getReferences(store, doc, new vscode.Position(0, 13), workspace);
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri: uri1, line: 0 },
|
||||
{ uri: uri1, line: 1 },
|
||||
{ uri: uri2, line: 0 },
|
||||
);
|
||||
}));
|
||||
|
||||
test('Should support finding references to autolinked http links', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`[1](http://example.com)`,
|
||||
`<http://example.com>`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const refs = await getReferences(store, doc, new vscode.Position(0, 13), workspace);
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri, line: 0 },
|
||||
{ uri, line: 1 },
|
||||
);
|
||||
}));
|
||||
|
||||
test('Should distinguish between references to file and to header within file', withStore(async (store) => {
|
||||
const docUri = workspacePath('doc.md');
|
||||
const other1Uri = workspacePath('sub', 'other.md');
|
||||
|
||||
const doc = new InMemoryDocument(docUri, joinLines(
|
||||
`# abc`,
|
||||
``,
|
||||
`[link 1](#abc)`,
|
||||
));
|
||||
const otherDoc = new InMemoryDocument(other1Uri, joinLines(
|
||||
`[link](/doc.md#abc)`,
|
||||
`[link no text](/doc#abc)`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([
|
||||
doc,
|
||||
otherDoc,
|
||||
]));
|
||||
|
||||
{
|
||||
// Check refs to header fragment
|
||||
const headerRefs = await getReferences(store, otherDoc, new vscode.Position(0, 16), workspace);
|
||||
assertReferencesEqual(headerRefs,
|
||||
{ uri: docUri, line: 0 }, // Header definition
|
||||
{ uri: docUri, line: 2 },
|
||||
{ uri: other1Uri, line: 0 },
|
||||
{ uri: other1Uri, line: 1 },
|
||||
);
|
||||
}
|
||||
{
|
||||
// Check refs to file itself from link with ext
|
||||
const fileRefs = await getReferences(store, otherDoc, new vscode.Position(0, 9), workspace);
|
||||
assertReferencesEqual(fileRefs,
|
||||
{ uri: other1Uri, line: 0, endCharacter: 14 },
|
||||
{ uri: other1Uri, line: 1, endCharacter: 19 },
|
||||
);
|
||||
}
|
||||
{
|
||||
// Check refs to file itself from link without ext
|
||||
const fileRefs = await getReferences(store, otherDoc, new vscode.Position(1, 17), workspace);
|
||||
assertReferencesEqual(fileRefs,
|
||||
{ uri: other1Uri, line: 0 },
|
||||
{ uri: other1Uri, line: 1 },
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
test('Should support finding references to unknown file', withStore(async (store) => {
|
||||
const uri1 = workspacePath('doc1.md');
|
||||
const doc1 = new InMemoryDocument(uri1, joinLines(
|
||||
`![img](/images/more/image.png)`,
|
||||
``,
|
||||
`[ref]: /images/more/image.png`,
|
||||
));
|
||||
|
||||
const uri2 = workspacePath('sub', 'doc2.md');
|
||||
const doc2 = new InMemoryDocument(uri2, joinLines(
|
||||
`![img](/images/more/image.png)`,
|
||||
));
|
||||
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc1, doc2]));
|
||||
|
||||
const refs = await getReferences(store, doc1, new vscode.Position(0, 10), workspace);
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri: uri1, line: 0 },
|
||||
{ uri: uri1, line: 2 },
|
||||
{ uri: uri2, line: 0 },
|
||||
);
|
||||
}));
|
||||
|
||||
suite('Reference links', () => {
|
||||
test('Should find reference links within file from link', withStore(async (store) => {
|
||||
const docUri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(docUri, joinLines(
|
||||
`[link 1][abc]`, // trigger here
|
||||
``,
|
||||
`[abc]: https://example.com`,
|
||||
));
|
||||
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const refs = await getReferences(store, doc, new vscode.Position(0, 12), workspace);
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri: docUri, line: 0 },
|
||||
{ uri: docUri, line: 2 },
|
||||
);
|
||||
}));
|
||||
|
||||
test('Should find reference links using shorthand', withStore(async (store) => {
|
||||
const docUri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(docUri, joinLines(
|
||||
`[ref]`, // trigger 1
|
||||
``,
|
||||
`[yes][ref]`, // trigger 2
|
||||
``,
|
||||
`[ref]: /Hello.md` // trigger 3
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
{
|
||||
const refs = await getReferences(store, doc, new vscode.Position(0, 2), workspace);
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri: docUri, line: 0 },
|
||||
{ uri: docUri, line: 2 },
|
||||
{ uri: docUri, line: 4 },
|
||||
);
|
||||
}
|
||||
{
|
||||
const refs = await getReferences(store, doc, new vscode.Position(2, 7), workspace);
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri: docUri, line: 0 },
|
||||
{ uri: docUri, line: 2 },
|
||||
{ uri: docUri, line: 4 },
|
||||
);
|
||||
}
|
||||
{
|
||||
const refs = await getReferences(store, doc, new vscode.Position(4, 2), workspace);
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri: docUri, line: 0 },
|
||||
{ uri: docUri, line: 2 },
|
||||
{ uri: docUri, line: 4 },
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
test('Should find reference links within file from definition', withStore(async (store) => {
|
||||
const docUri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(docUri, joinLines(
|
||||
`[link 1][abc]`,
|
||||
``,
|
||||
`[abc]: https://example.com`, // trigger here
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const refs = await getReferences(store, doc, new vscode.Position(2, 3), workspace);
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri: docUri, line: 0 },
|
||||
{ uri: docUri, line: 2 },
|
||||
);
|
||||
}));
|
||||
|
||||
test('Should not find reference links across files', withStore(async (store) => {
|
||||
const docUri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(docUri, joinLines(
|
||||
`[link 1][abc]`,
|
||||
``,
|
||||
`[abc]: https://example.com`,
|
||||
));
|
||||
|
||||
const workspace = store.add(new InMemoryMdWorkspace([
|
||||
doc,
|
||||
new InMemoryDocument(workspacePath('other.md'), joinLines(
|
||||
`[link 1][abc]`,
|
||||
``,
|
||||
`[abc]: https://example.com?bad`
|
||||
))
|
||||
]));
|
||||
|
||||
const refs = await getReferences(store, doc, new vscode.Position(0, 12), workspace);
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri: docUri, line: 0 },
|
||||
{ uri: docUri, line: 2 },
|
||||
);
|
||||
}));
|
||||
|
||||
test('Should not consider checkboxes as reference links', withStore(async (store) => {
|
||||
const docUri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(docUri, joinLines(
|
||||
`- [x]`,
|
||||
`- [X]`,
|
||||
`- [ ]`,
|
||||
``,
|
||||
`[x]: https://example.com`
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const refs = await getReferences(store, doc, new vscode.Position(0, 4), workspace);
|
||||
assert.strictEqual(refs?.length!, 0);
|
||||
}));
|
||||
});
|
||||
});
|
|
@ -1,720 +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 { MdReferencesProvider } from '../languageFeatures/references';
|
||||
import { MdVsCodeRenameProvider, MdWorkspaceEdit } from '../languageFeatures/rename';
|
||||
import { githubSlugifier } from '../slugify';
|
||||
import { MdTableOfContentsProvider } from '../tableOfContents';
|
||||
import { noopToken } from '../util/cancellation';
|
||||
import { DisposableStore } from '../util/dispose';
|
||||
import { InMemoryDocument } from '../util/inMemoryDocument';
|
||||
import { IMdWorkspace } from '../workspace';
|
||||
import { createNewMarkdownEngine } from './engine';
|
||||
import { InMemoryMdWorkspace } from './inMemoryWorkspace';
|
||||
import { nulLogger } from './nulLogging';
|
||||
import { assertRangeEqual, joinLines, withStore, workspacePath } from './util';
|
||||
|
||||
|
||||
/**
|
||||
* Get prepare rename info.
|
||||
*/
|
||||
function prepareRename(store: DisposableStore, doc: InMemoryDocument, pos: vscode.Position, workspace: IMdWorkspace): Promise<undefined | { readonly range: vscode.Range; readonly placeholder: string }> {
|
||||
const engine = createNewMarkdownEngine();
|
||||
const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger));
|
||||
const referenceComputer = store.add(new MdReferencesProvider(engine, workspace, tocProvider, nulLogger));
|
||||
const renameProvider = store.add(new MdVsCodeRenameProvider(workspace, referenceComputer, githubSlugifier));
|
||||
return renameProvider.prepareRename(doc, pos, noopToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the edits for the rename.
|
||||
*/
|
||||
function getRenameEdits(store: DisposableStore, doc: InMemoryDocument, pos: vscode.Position, newName: string, workspace: IMdWorkspace): Promise<MdWorkspaceEdit | undefined> {
|
||||
const engine = createNewMarkdownEngine();
|
||||
const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger));
|
||||
const referencesProvider = store.add(new MdReferencesProvider(engine, workspace, tocProvider, nulLogger));
|
||||
const renameProvider = store.add(new MdVsCodeRenameProvider(workspace, referencesProvider, githubSlugifier));
|
||||
return renameProvider.provideRenameEditsImpl(doc, pos, newName, noopToken);
|
||||
}
|
||||
|
||||
interface ExpectedTextEdit {
|
||||
readonly uri: vscode.Uri;
|
||||
readonly edits: readonly vscode.TextEdit[];
|
||||
}
|
||||
|
||||
interface ExpectedFileRename {
|
||||
readonly originalUri: vscode.Uri;
|
||||
readonly newUri: vscode.Uri;
|
||||
}
|
||||
|
||||
function assertEditsEqual(actualEdit: MdWorkspaceEdit, ...expectedEdits: ReadonlyArray<ExpectedTextEdit | ExpectedFileRename>) {
|
||||
// Check file renames
|
||||
const expectedFileRenames = expectedEdits.filter(expected => 'originalUri' in expected) as ExpectedFileRename[];
|
||||
const actualFileRenames = actualEdit.fileRenames ?? [];
|
||||
assert.strictEqual(actualFileRenames.length, expectedFileRenames.length, `File rename count should match`);
|
||||
for (let i = 0; i < actualFileRenames.length; ++i) {
|
||||
const expected = expectedFileRenames[i];
|
||||
const actual = actualFileRenames[i];
|
||||
assert.strictEqual(actual.from.toString(), expected.originalUri.toString(), `File rename '${i}' should have expected 'from' resource`);
|
||||
assert.strictEqual(actual.to.toString(), expected.newUri.toString(), `File rename '${i}' should have expected 'to' resource`);
|
||||
}
|
||||
|
||||
// Check text edits
|
||||
const actualTextEdits = actualEdit.edit.entries();
|
||||
const expectedTextEdits = expectedEdits.filter(expected => 'edits' in expected) as ExpectedTextEdit[];
|
||||
assert.strictEqual(actualTextEdits.length, expectedTextEdits.length, `Reference counts should match`);
|
||||
for (let i = 0; i < actualTextEdits.length; ++i) {
|
||||
const expected = expectedTextEdits[i];
|
||||
const actual = actualTextEdits[i];
|
||||
|
||||
if ('edits' in expected) {
|
||||
assert.strictEqual(actual[0].toString(), expected.uri.toString(), `Ref '${i}' has expected document`);
|
||||
|
||||
const actualEditForDoc = actual[1];
|
||||
const expectedEditsForDoc = expected.edits;
|
||||
assert.strictEqual(actualEditForDoc.length, expectedEditsForDoc.length, `Edit counts for '${actual[0]}' should match`);
|
||||
|
||||
for (let g = 0; g < actualEditForDoc.length; ++g) {
|
||||
assertRangeEqual(actualEditForDoc[g].range, expectedEditsForDoc[g].range, `Edit '${g}' of '${actual[0]}' has expected expected range. Expected range: ${JSON.stringify(actualEditForDoc[g].range)}. Actual range: ${JSON.stringify(expectedEditsForDoc[g].range)}`);
|
||||
assert.strictEqual(actualEditForDoc[g].newText, expectedEditsForDoc[g].newText, `Edit '${g}' of '${actual[0]}' has expected edits`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suite('markdown: rename', () => {
|
||||
|
||||
setup(async () => {
|
||||
// the tests make the assumption that link providers are already registered
|
||||
await vscode.extensions.getExtension('vscode.markdown-language-features')!.activate();
|
||||
});
|
||||
|
||||
test('Rename on header should not include leading #', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`# abc`
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const info = await prepareRename(store, doc, new vscode.Position(0, 0), workspace);
|
||||
assertRangeEqual(info!.range, new vscode.Range(0, 2, 0, 5));
|
||||
|
||||
const edit = await getRenameEdits(store, doc, new vscode.Position(0, 0), "New Header", workspace);
|
||||
assertEditsEqual(edit!, {
|
||||
uri, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(0, 2, 0, 5), 'New Header')
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
test('Rename on header should include leading or trailing #s', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`### abc ###`
|
||||
));
|
||||
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const info = await prepareRename(store, doc, new vscode.Position(0, 0), workspace);
|
||||
assertRangeEqual(info!.range, new vscode.Range(0, 4, 0, 7));
|
||||
|
||||
const edit = await getRenameEdits(store, doc, new vscode.Position(0, 0), "New Header", workspace);
|
||||
assertEditsEqual(edit!, {
|
||||
uri, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(0, 4, 0, 7), 'New Header')
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
test('Rename on header should pick up links in doc', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`### A b C`, // rename here
|
||||
`[text](#a-b-c)`,
|
||||
));
|
||||
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
const edit = await getRenameEdits(store, doc, new vscode.Position(0, 0), "New Header", workspace);
|
||||
assertEditsEqual(edit!, {
|
||||
uri, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'),
|
||||
new vscode.TextEdit(new vscode.Range(1, 8, 1, 13), 'new-header'),
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
test('Rename on link should use slug for link', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`### A b C`,
|
||||
`[text](#a-b-c)`, // rename here
|
||||
));
|
||||
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
const edit = await getRenameEdits(store, doc, new vscode.Position(1, 10), "New Header", workspace);
|
||||
assertEditsEqual(edit!, {
|
||||
uri, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'),
|
||||
new vscode.TextEdit(new vscode.Range(1, 8, 1, 13), 'new-header'),
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
test('Rename on link definition should work', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`### A b C`,
|
||||
`[text](#a-b-c)`,
|
||||
`[ref]: #a-b-c`// rename here
|
||||
));
|
||||
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
const edit = await getRenameEdits(store, doc, new vscode.Position(2, 10), "New Header", workspace);
|
||||
assertEditsEqual(edit!, {
|
||||
uri, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'),
|
||||
new vscode.TextEdit(new vscode.Range(1, 8, 1, 13), 'new-header'),
|
||||
new vscode.TextEdit(new vscode.Range(2, 8, 2, 13), 'new-header'),
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
test('Rename on header should pick up links across files', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const otherUri = workspacePath('other.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`### A b C`, // rename here
|
||||
`[text](#a-b-c)`,
|
||||
));
|
||||
|
||||
const edit = await getRenameEdits(store, doc, new vscode.Position(0, 0), "New Header", new InMemoryMdWorkspace([
|
||||
doc,
|
||||
new InMemoryDocument(otherUri, joinLines(
|
||||
`[text](#a-b-c)`, // Should not find this
|
||||
`[text](./doc.md#a-b-c)`, // But should find this
|
||||
`[text](./doc#a-b-c)`, // And this
|
||||
))
|
||||
]));
|
||||
assertEditsEqual(edit!, {
|
||||
uri: uri, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'),
|
||||
new vscode.TextEdit(new vscode.Range(1, 8, 1, 13), 'new-header'),
|
||||
]
|
||||
}, {
|
||||
uri: otherUri, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(1, 16, 1, 21), 'new-header'),
|
||||
new vscode.TextEdit(new vscode.Range(2, 13, 2, 18), 'new-header'),
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
test('Rename on link should pick up links across files', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const otherUri = workspacePath('other.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`### A b C`,
|
||||
`[text](#a-b-c)`, // rename here
|
||||
));
|
||||
|
||||
const edit = await getRenameEdits(store, doc, new vscode.Position(1, 10), "New Header", new InMemoryMdWorkspace([
|
||||
doc,
|
||||
new InMemoryDocument(otherUri, joinLines(
|
||||
`[text](#a-b-c)`, // Should not find this
|
||||
`[text](./doc.md#a-b-c)`, // But should find this
|
||||
`[text](./doc#a-b-c)`, // And this
|
||||
))
|
||||
]));
|
||||
assertEditsEqual(edit!, {
|
||||
uri: uri, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'),
|
||||
new vscode.TextEdit(new vscode.Range(1, 8, 1, 13), 'new-header'),
|
||||
]
|
||||
}, {
|
||||
uri: otherUri, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(1, 16, 1, 21), 'new-header'),
|
||||
new vscode.TextEdit(new vscode.Range(2, 13, 2, 18), 'new-header'),
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
test('Rename on link in other file should pick up all refs', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const otherUri = workspacePath('other.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`### A b C`,
|
||||
`[text](#a-b-c)`,
|
||||
));
|
||||
|
||||
const otherDoc = new InMemoryDocument(otherUri, joinLines(
|
||||
`[text](#a-b-c)`,
|
||||
`[text](./doc.md#a-b-c)`,
|
||||
`[text](./doc#a-b-c)`
|
||||
));
|
||||
|
||||
const expectedEdits = [
|
||||
{
|
||||
uri: uri, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'),
|
||||
new vscode.TextEdit(new vscode.Range(1, 8, 1, 13), 'new-header'),
|
||||
]
|
||||
}, {
|
||||
uri: otherUri, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(1, 16, 1, 21), 'new-header'),
|
||||
new vscode.TextEdit(new vscode.Range(2, 13, 2, 18), 'new-header'),
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
{
|
||||
// Rename on header with file extension
|
||||
const edit = await getRenameEdits(store, otherDoc, new vscode.Position(1, 17), "New Header", new InMemoryMdWorkspace([
|
||||
doc,
|
||||
otherDoc
|
||||
]));
|
||||
assertEditsEqual(edit!, ...expectedEdits);
|
||||
}
|
||||
{
|
||||
// Rename on header without extension
|
||||
const edit = await getRenameEdits(store, otherDoc, new vscode.Position(2, 15), "New Header", new InMemoryMdWorkspace([
|
||||
doc,
|
||||
otherDoc
|
||||
]));
|
||||
assertEditsEqual(edit!, ...expectedEdits);
|
||||
}
|
||||
}));
|
||||
|
||||
test('Rename on reference should rename references and definition', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`[text][ref]`, // rename here
|
||||
`[other][ref]`,
|
||||
``,
|
||||
`[ref]: https://example.com`,
|
||||
));
|
||||
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
const edit = await getRenameEdits(store, doc, new vscode.Position(0, 8), "new ref", workspace);
|
||||
assertEditsEqual(edit!, {
|
||||
uri, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(0, 7, 0, 10), 'new ref'),
|
||||
new vscode.TextEdit(new vscode.Range(1, 8, 1, 11), 'new ref'),
|
||||
new vscode.TextEdit(new vscode.Range(3, 1, 3, 4), 'new ref'),
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
test('Rename on definition should rename references and definitions', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`[text][ref]`,
|
||||
`[other][ref]`,
|
||||
``,
|
||||
`[ref]: https://example.com`, // rename here
|
||||
));
|
||||
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
const edit = await getRenameEdits(store, doc, new vscode.Position(3, 3), "new ref", workspace);
|
||||
assertEditsEqual(edit!, {
|
||||
uri, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(0, 7, 0, 10), 'new ref'),
|
||||
new vscode.TextEdit(new vscode.Range(1, 8, 1, 11), 'new ref'),
|
||||
new vscode.TextEdit(new vscode.Range(3, 1, 3, 4), 'new ref'),
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
test('Rename on definition entry should rename header and references', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`# a B c`,
|
||||
`[ref text][ref]`,
|
||||
`[direct](#a-b-c)`,
|
||||
`[ref]: #a-b-c`, // rename here
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const preparedInfo = await prepareRename(store, doc, new vscode.Position(3, 10), workspace);
|
||||
assert.strictEqual(preparedInfo!.placeholder, 'a B c');
|
||||
assertRangeEqual(preparedInfo!.range, new vscode.Range(3, 8, 3, 13));
|
||||
|
||||
const edit = await getRenameEdits(store, doc, new vscode.Position(3, 10), "x Y z", workspace);
|
||||
assertEditsEqual(edit!, {
|
||||
uri, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(0, 2, 0, 7), 'x Y z'),
|
||||
new vscode.TextEdit(new vscode.Range(2, 10, 2, 15), 'x-y-z'),
|
||||
new vscode.TextEdit(new vscode.Range(3, 8, 3, 13), 'x-y-z'),
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
test('Rename should not be supported on link text', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`# Header`,
|
||||
`[text](#header)`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
await assert.rejects(prepareRename(store, doc, new vscode.Position(1, 2), workspace));
|
||||
}));
|
||||
|
||||
test('Path rename should use file path as range', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`[text](./doc.md)`,
|
||||
`[ref]: ./doc.md`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const info = await prepareRename(store, doc, new vscode.Position(0, 10), workspace);
|
||||
assert.strictEqual(info!.placeholder, './doc.md');
|
||||
assertRangeEqual(info!.range, new vscode.Range(0, 7, 0, 15));
|
||||
}));
|
||||
|
||||
test('Path rename\'s range should excludes fragment', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`[text](./doc.md#some-header)`,
|
||||
`[ref]: ./doc.md#some-header`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const info = await prepareRename(store, doc, new vscode.Position(0, 10), workspace);
|
||||
assert.strictEqual(info!.placeholder, './doc.md');
|
||||
assertRangeEqual(info!.range, new vscode.Range(0, 7, 0, 15));
|
||||
}));
|
||||
|
||||
test('Path rename should update file and all refs', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`[text](./doc.md)`,
|
||||
`[ref]: ./doc.md`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const edit = await getRenameEdits(store, doc, new vscode.Position(0, 10), './sub/newDoc.md', workspace);
|
||||
assertEditsEqual(edit!, {
|
||||
originalUri: uri,
|
||||
newUri: workspacePath('sub', 'newDoc.md'),
|
||||
}, {
|
||||
uri: uri, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(0, 7, 0, 15), './sub/newDoc.md'),
|
||||
new vscode.TextEdit(new vscode.Range(1, 7, 1, 15), './sub/newDoc.md'),
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
test('Path rename using absolute file path should anchor to workspace root', withStore(async (store) => {
|
||||
const uri = workspacePath('sub', 'doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`[text](/sub/doc.md)`,
|
||||
`[ref]: /sub/doc.md`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const edit = await getRenameEdits(store, doc, new vscode.Position(0, 10), '/newSub/newDoc.md', workspace);
|
||||
assertEditsEqual(edit!, {
|
||||
originalUri: uri,
|
||||
newUri: workspacePath('newSub', 'newDoc.md'),
|
||||
}, {
|
||||
uri: uri, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(0, 7, 0, 18), '/newSub/newDoc.md'),
|
||||
new vscode.TextEdit(new vscode.Range(1, 7, 1, 18), '/newSub/newDoc.md'),
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
test('Path rename should use un-encoded paths as placeholder', withStore(async (store) => {
|
||||
const uri = workspacePath('sub', 'doc with spaces.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`[text](/sub/doc%20with%20spaces.md)`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const info = await prepareRename(store, doc, new vscode.Position(0, 10), workspace);
|
||||
assert.strictEqual(info!.placeholder, '/sub/doc with spaces.md');
|
||||
}));
|
||||
|
||||
test('Path rename should encode paths', withStore(async (store) => {
|
||||
const uri = workspacePath('sub', 'doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`[text](/sub/doc.md)`,
|
||||
`[ref]: /sub/doc.md`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const edit = await getRenameEdits(store, doc, new vscode.Position(0, 10), '/NEW sub/new DOC.md', workspace);
|
||||
assertEditsEqual(edit!, {
|
||||
originalUri: uri,
|
||||
newUri: workspacePath('NEW sub', 'new DOC.md'),
|
||||
}, {
|
||||
uri: uri, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(0, 7, 0, 18), '/NEW%20sub/new%20DOC.md'),
|
||||
new vscode.TextEdit(new vscode.Range(1, 7, 1, 18), '/NEW%20sub/new%20DOC.md'),
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
test('Path rename should work with unknown files', withStore(async (store) => {
|
||||
const uri1 = workspacePath('doc1.md');
|
||||
const doc1 = new InMemoryDocument(uri1, joinLines(
|
||||
`![img](/images/more/image.png)`,
|
||||
``,
|
||||
`[ref]: /images/more/image.png`,
|
||||
));
|
||||
|
||||
const uri2 = workspacePath('sub', 'doc2.md');
|
||||
const doc2 = new InMemoryDocument(uri2, joinLines(
|
||||
`![img](/images/more/image.png)`,
|
||||
));
|
||||
|
||||
const workspace = store.add(new InMemoryMdWorkspace([
|
||||
doc1,
|
||||
doc2
|
||||
]));
|
||||
|
||||
const edit = await getRenameEdits(store, doc1, new vscode.Position(0, 10), '/img/test/new.png', workspace);
|
||||
assertEditsEqual(edit!,
|
||||
// Should not have file edits since the files don't exist here
|
||||
{
|
||||
uri: uri1, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(0, 7, 0, 29), '/img/test/new.png'),
|
||||
new vscode.TextEdit(new vscode.Range(2, 7, 2, 29), '/img/test/new.png'),
|
||||
]
|
||||
},
|
||||
{
|
||||
uri: uri2, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(0, 7, 0, 29), '/img/test/new.png'),
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
test('Path rename should use .md extension on extension-less link', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`[text](/doc#header)`,
|
||||
`[ref]: /doc#other`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const edit = await getRenameEdits(store, doc, new vscode.Position(0, 10), '/new File', workspace);
|
||||
assertEditsEqual(edit!, {
|
||||
originalUri: uri,
|
||||
newUri: workspacePath('new File.md'), // Rename on disk should use file extension
|
||||
}, {
|
||||
uri: uri, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(0, 7, 0, 11), '/new%20File'), // Links should continue to use extension-less paths
|
||||
new vscode.TextEdit(new vscode.Range(1, 7, 1, 11), '/new%20File'),
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
// TODO: fails on windows
|
||||
test.skip('Path rename should use correctly resolved paths across files', withStore(async (store) => {
|
||||
const uri1 = workspacePath('sub', 'doc.md');
|
||||
const doc1 = new InMemoryDocument(uri1, joinLines(
|
||||
`[text](./doc.md)`,
|
||||
`[ref]: ./doc.md`,
|
||||
));
|
||||
|
||||
const uri2 = workspacePath('doc2.md');
|
||||
const doc2 = new InMemoryDocument(uri2, joinLines(
|
||||
`[text](./sub/doc.md)`,
|
||||
`[ref]: ./sub/doc.md`,
|
||||
));
|
||||
|
||||
const uri3 = workspacePath('sub2', 'doc3.md');
|
||||
const doc3 = new InMemoryDocument(uri3, joinLines(
|
||||
`[text](../sub/doc.md)`,
|
||||
`[ref]: ../sub/doc.md`,
|
||||
));
|
||||
|
||||
const uri4 = workspacePath('sub2', 'doc4.md');
|
||||
const doc4 = new InMemoryDocument(uri4, joinLines(
|
||||
`[text](/sub/doc.md)`,
|
||||
`[ref]: /sub/doc.md`,
|
||||
));
|
||||
|
||||
const workspace = store.add(new InMemoryMdWorkspace([
|
||||
doc1, doc2, doc3, doc4,
|
||||
]));
|
||||
|
||||
const edit = await getRenameEdits(store, doc1, new vscode.Position(0, 10), './new/new-doc.md', workspace);
|
||||
assertEditsEqual(edit!, {
|
||||
originalUri: uri1,
|
||||
newUri: workspacePath('sub', 'new', 'new-doc.md'),
|
||||
}, {
|
||||
uri: uri1, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(0, 7, 0, 15), './new/new-doc.md'),
|
||||
new vscode.TextEdit(new vscode.Range(1, 7, 1, 15), './new/new-doc.md'),
|
||||
]
|
||||
}, {
|
||||
uri: uri2, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(0, 7, 0, 19), './sub/new/new-doc.md'),
|
||||
new vscode.TextEdit(new vscode.Range(1, 7, 1, 19), './sub/new/new-doc.md'),
|
||||
]
|
||||
}, {
|
||||
uri: uri3, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(0, 7, 0, 20), '../sub/new/new-doc.md'),
|
||||
new vscode.TextEdit(new vscode.Range(1, 7, 1, 20), '../sub/new/new-doc.md'),
|
||||
]
|
||||
}, {
|
||||
uri: uri4, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(0, 7, 0, 18), '/sub/new/new-doc.md'),
|
||||
new vscode.TextEdit(new vscode.Range(1, 7, 1, 18), '/sub/new/new-doc.md'),
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
test('Path rename should resolve on links without prefix', withStore(async (store) => {
|
||||
const uri1 = workspacePath('sub', 'doc.md');
|
||||
const doc1 = new InMemoryDocument(uri1, joinLines(
|
||||
`![text](sub2/doc3.md)`,
|
||||
));
|
||||
|
||||
const uri2 = workspacePath('doc2.md');
|
||||
const doc2 = new InMemoryDocument(uri2, joinLines(
|
||||
`![text](sub/sub2/doc3.md)`,
|
||||
));
|
||||
|
||||
const uri3 = workspacePath('sub', 'sub2', 'doc3.md');
|
||||
const doc3 = new InMemoryDocument(uri3, joinLines());
|
||||
|
||||
const workspace = store.add(new InMemoryMdWorkspace([
|
||||
doc1, doc2, doc3
|
||||
]));
|
||||
|
||||
const edit = await getRenameEdits(store, doc1, new vscode.Position(0, 10), 'sub2/cat.md', workspace);
|
||||
assertEditsEqual(edit!, {
|
||||
originalUri: workspacePath('sub', 'sub2', 'doc3.md'),
|
||||
newUri: workspacePath('sub', 'sub2', 'cat.md'),
|
||||
}, {
|
||||
uri: uri1, edits: [new vscode.TextEdit(new vscode.Range(0, 8, 0, 20), 'sub2/cat.md')]
|
||||
}, {
|
||||
uri: uri2, edits: [new vscode.TextEdit(new vscode.Range(0, 8, 0, 24), 'sub/sub2/cat.md')]
|
||||
});
|
||||
}));
|
||||
|
||||
test('Rename on link should use header text as placeholder', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`### a B c ###`,
|
||||
`[text](#a-b-c)`,
|
||||
));
|
||||
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
const info = await prepareRename(store, doc, new vscode.Position(1, 10), workspace);
|
||||
assert.strictEqual(info!.placeholder, 'a B c');
|
||||
assertRangeEqual(info!.range, new vscode.Range(1, 8, 1, 13));
|
||||
}));
|
||||
|
||||
test('Rename on http uri should work', withStore(async (store) => {
|
||||
const uri1 = workspacePath('doc.md');
|
||||
const uri2 = workspacePath('doc2.md');
|
||||
const doc = new InMemoryDocument(uri1, joinLines(
|
||||
`[1](http://example.com)`,
|
||||
`[2]: http://example.com`,
|
||||
`<http://example.com>`,
|
||||
));
|
||||
|
||||
const workspace = store.add(new InMemoryMdWorkspace([
|
||||
doc,
|
||||
new InMemoryDocument(uri2, joinLines(
|
||||
`[4](http://example.com)`
|
||||
))
|
||||
]));
|
||||
|
||||
const edit = await getRenameEdits(store, doc, new vscode.Position(1, 10), "https://example.com/sub", workspace);
|
||||
assertEditsEqual(edit!, {
|
||||
uri: uri1, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(0, 4, 0, 22), 'https://example.com/sub'),
|
||||
new vscode.TextEdit(new vscode.Range(1, 5, 1, 23), 'https://example.com/sub'),
|
||||
new vscode.TextEdit(new vscode.Range(2, 1, 2, 19), 'https://example.com/sub'),
|
||||
]
|
||||
}, {
|
||||
uri: uri2, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(0, 4, 0, 22), 'https://example.com/sub'),
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
test('Rename on definition path should update all references to path', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`[ref text][ref]`,
|
||||
`[direct](/file)`,
|
||||
`[ref]: /file`, // rename here
|
||||
));
|
||||
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const preparedInfo = await prepareRename(store, doc, new vscode.Position(2, 10), workspace);
|
||||
assert.strictEqual(preparedInfo!.placeholder, '/file');
|
||||
assertRangeEqual(preparedInfo!.range, new vscode.Range(2, 7, 2, 12));
|
||||
|
||||
const edit = await getRenameEdits(store, doc, new vscode.Position(2, 10), "/newFile", workspace);
|
||||
assertEditsEqual(edit!, {
|
||||
uri, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(1, 9, 1, 14), '/newFile'),
|
||||
new vscode.TextEdit(new vscode.Range(2, 7, 2, 12), '/newFile'),
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
test('Rename on definition path where file exists should also update file', withStore(async (store) => {
|
||||
const uri1 = workspacePath('doc.md');
|
||||
const doc1 = new InMemoryDocument(uri1, joinLines(
|
||||
`[ref text][ref]`,
|
||||
`[direct](/doc2)`,
|
||||
`[ref]: /doc2`, // rename here
|
||||
));
|
||||
|
||||
const uri2 = workspacePath('doc2.md');
|
||||
const doc2 = new InMemoryDocument(uri2, joinLines());
|
||||
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc1, doc2]));
|
||||
|
||||
const preparedInfo = await prepareRename(store, doc1, new vscode.Position(2, 10), workspace);
|
||||
assert.strictEqual(preparedInfo!.placeholder, '/doc2');
|
||||
assertRangeEqual(preparedInfo!.range, new vscode.Range(2, 7, 2, 12));
|
||||
|
||||
const edit = await getRenameEdits(store, doc1, new vscode.Position(2, 10), "/new-doc", workspace);
|
||||
assertEditsEqual(edit!, {
|
||||
uri: uri1, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(1, 9, 1, 14), '/new-doc'),
|
||||
new vscode.TextEdit(new vscode.Range(2, 7, 2, 12), '/new-doc'),
|
||||
]
|
||||
}, {
|
||||
originalUri: uri2,
|
||||
newUri: workspacePath('new-doc.md')
|
||||
});
|
||||
}));
|
||||
|
||||
test('Rename on definition path header should update all references to header', withStore(async (store) => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`[ref text][ref]`,
|
||||
`[direct](/file#header)`,
|
||||
`[ref]: /file#header`, // rename here
|
||||
));
|
||||
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const preparedInfo = await prepareRename(store, doc, new vscode.Position(2, 16), workspace);
|
||||
assert.strictEqual(preparedInfo!.placeholder, 'header');
|
||||
assertRangeEqual(preparedInfo!.range, new vscode.Range(2, 13, 2, 19));
|
||||
|
||||
const edit = await getRenameEdits(store, doc, new vscode.Position(2, 16), "New Header", workspace);
|
||||
assertEditsEqual(edit!, {
|
||||
uri, edits: [
|
||||
new vscode.TextEdit(new vscode.Range(1, 15, 1, 21), 'new-header'),
|
||||
new vscode.TextEdit(new vscode.Range(2, 13, 2, 19), 'new-header'),
|
||||
]
|
||||
});
|
||||
}));
|
||||
});
|
|
@ -6,28 +6,11 @@ import * as assert from 'assert';
|
|||
import * as os from 'os';
|
||||
import * as vscode from 'vscode';
|
||||
import { DisposableStore } from '../util/dispose';
|
||||
import { InMemoryDocument } from '../util/inMemoryDocument';
|
||||
|
||||
export const joinLines = (...args: string[]) =>
|
||||
args.join(os.platform() === 'win32' ? '\r\n' : '\n');
|
||||
|
||||
|
||||
export const CURSOR = '$$CURSOR$$';
|
||||
|
||||
export function getCursorPositions(contents: string, doc: InMemoryDocument): vscode.Position[] {
|
||||
const positions: vscode.Position[] = [];
|
||||
let index = 0;
|
||||
let wordLength = 0;
|
||||
while (index !== -1) {
|
||||
index = contents.indexOf(CURSOR, index + wordLength);
|
||||
if (index !== -1) {
|
||||
positions.push(doc.positionAt(index));
|
||||
}
|
||||
wordLength = CURSOR.length;
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
|
||||
export function workspacePath(...segments: string[]): vscode.Uri {
|
||||
return vscode.Uri.joinPath(vscode.workspace.workspaceFolders![0].uri, ...segments);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
"include": [
|
||||
"src/**/*",
|
||||
"../../src/vscode-dts/vscode.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.textEditorDrop.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.documentPaste.d.ts"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -242,6 +242,11 @@ vscode-languageserver-types@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:
|
||||
version "3.17.2"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz#b2c2e7de405ad3d73a883e91989b850170ffc4f2"
|
||||
integrity sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA==
|
||||
|
||||
vscode-nls@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840"
|
||||
|
|
|
@ -13,7 +13,7 @@ const fs = require('fs');
|
|||
const merge = require('merge-options');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const { NLSBundlePlugin } = require('vscode-nls-dev/lib/webpack-bundler');
|
||||
const { DefinePlugin } = require('webpack');
|
||||
const { DefinePlugin, optimize } = require('webpack');
|
||||
|
||||
function withNodeDefaults(/**@type WebpackConfig*/extConfig) {
|
||||
/** @type WebpackConfig */
|
||||
|
@ -145,6 +145,9 @@ function withBrowserDefaults(/**@type WebpackConfig*/extConfig, /** @type Additi
|
|||
}
|
||||
|
||||
const browserPlugins = [
|
||||
new optimize.LimitChunkCountPlugin({
|
||||
maxChunks: 1
|
||||
}),
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{ from: 'src', to: '.', globOptions: { ignore: ['**/test/**', '**/*.ts'] }, noErrorOnMissing: true }
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "code-oss-dev",
|
||||
"version": "1.70.0",
|
||||
"distro": "1a629baefa2ce65ed9d03176536e957c80bf6703",
|
||||
"distro": "1a72c46622967eab6ea48516a2153c55d7e18e53",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
},
|
||||
|
@ -85,12 +85,12 @@
|
|||
"vscode-proxy-agent": "^0.12.0",
|
||||
"vscode-regexpp": "^3.1.0",
|
||||
"vscode-textmate": "7.0.1",
|
||||
"xterm": "4.20.0-beta.12",
|
||||
"xterm": "4.20.0-beta.13",
|
||||
"xterm-addon-search": "0.10.0-beta.2",
|
||||
"xterm-addon-serialize": "0.8.0-beta.2",
|
||||
"xterm-addon-unicode11": "0.4.0-beta.3",
|
||||
"xterm-addon-webgl": "0.13.0-beta.6",
|
||||
"xterm-headless": "4.20.0-beta.12",
|
||||
"xterm-addon-webgl": "0.13.0-beta.7",
|
||||
"xterm-headless": "4.20.0-beta.13",
|
||||
"yauzl": "^2.9.2",
|
||||
"yazl": "^2.4.3"
|
||||
},
|
||||
|
|
|
@ -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.12",
|
||||
"xterm": "4.20.0-beta.13",
|
||||
"xterm-addon-search": "0.10.0-beta.2",
|
||||
"xterm-addon-serialize": "0.8.0-beta.2",
|
||||
"xterm-addon-unicode11": "0.4.0-beta.3",
|
||||
"xterm-addon-webgl": "0.13.0-beta.6",
|
||||
"xterm-headless": "4.20.0-beta.12",
|
||||
"xterm-addon-webgl": "0.13.0-beta.7",
|
||||
"xterm-headless": "4.20.0-beta.13",
|
||||
"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.12",
|
||||
"xterm": "4.20.0-beta.13",
|
||||
"xterm-addon-search": "0.10.0-beta.2",
|
||||
"xterm-addon-unicode11": "0.4.0-beta.3",
|
||||
"xterm-addon-webgl": "0.13.0-beta.6"
|
||||
"xterm-addon-webgl": "0.13.0-beta.7"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,12 +78,12 @@ xterm-addon-unicode11@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.6:
|
||||
version "0.13.0-beta.6"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.6.tgz#44eceb1e15a711159bdf1c2a779422aa11becabc"
|
||||
integrity sha512-83bo12rqYU04agC6rn+drEui8tarN5Ev66sdu86aGzxM+Ylr8wFqIb/Px/caX9qTqO79TN80ID7EC5P5QyL8XA==
|
||||
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@4.20.0-beta.12:
|
||||
version "4.20.0-beta.12"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.12.tgz#02751473b307d6795e6f47abf6798993128a84b7"
|
||||
integrity sha512-6bqZshNOJsghzQ5f52JIPrZL8MPGW+caQkeQ3aoAPleGvZz775LDQAQH2KzdR9XxOJI5wnJcxx+dk7IjulCsnw==
|
||||
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==
|
||||
|
|
|
@ -803,20 +803,20 @@ xterm-addon-unicode11@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.6:
|
||||
version "0.13.0-beta.6"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.6.tgz#44eceb1e15a711159bdf1c2a779422aa11becabc"
|
||||
integrity sha512-83bo12rqYU04agC6rn+drEui8tarN5Ev66sdu86aGzxM+Ylr8wFqIb/Px/caX9qTqO79TN80ID7EC5P5QyL8XA==
|
||||
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-headless@4.20.0-beta.12:
|
||||
version "4.20.0-beta.12"
|
||||
resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.20.0-beta.12.tgz#a8f14127212ef15b1d4e726014daf422d29d06ba"
|
||||
integrity sha512-MtxrRy1qm/SQl5oTClK30rzip/WELzkGQ837CiOlGLiktj5yA1gK7SA7T532fIPPa9czJ8jBuONvZQJL3M000A==
|
||||
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@4.20.0-beta.12:
|
||||
version "4.20.0-beta.12"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.12.tgz#02751473b307d6795e6f47abf6798993128a84b7"
|
||||
integrity sha512-6bqZshNOJsghzQ5f52JIPrZL8MPGW+caQkeQ3aoAPleGvZz775LDQAQH2KzdR9XxOJI5wnJcxx+dk7IjulCsnw==
|
||||
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==
|
||||
|
||||
yallist@^4.0.0:
|
||||
version "4.0.0"
|
||||
|
|
13
src/main.js
13
src/main.js
|
@ -153,9 +153,6 @@ function configureCommandlineSwitchesSync(cliArgs) {
|
|||
// alias from us for --disable-gpu
|
||||
'disable-hardware-acceleration',
|
||||
|
||||
// provided by Electron
|
||||
'disable-color-correct-rendering',
|
||||
|
||||
// override for the color profile to use
|
||||
'force-color-profile'
|
||||
];
|
||||
|
@ -247,9 +244,7 @@ function readArgvConfigSync() {
|
|||
|
||||
// Fallback to default
|
||||
if (!argvConfig) {
|
||||
argvConfig = {
|
||||
'disable-color-correct-rendering': true // Force pre-Chrome-60 color profile handling (for https://github.com/microsoft/vscode/issues/51791)
|
||||
};
|
||||
argvConfig = {};
|
||||
}
|
||||
|
||||
return argvConfig;
|
||||
|
@ -279,11 +274,7 @@ function createDefaultArgvConfigSync(argvConfigPath) {
|
|||
'{',
|
||||
' // Use software rendering instead of hardware accelerated rendering.',
|
||||
' // This can help in cases where you see rendering issues in VS Code.',
|
||||
' // "disable-hardware-acceleration": true,',
|
||||
'',
|
||||
' // Enabled by default by VS Code to resolve color issues in the renderer',
|
||||
' // See https://github.com/microsoft/vscode/issues/51791 for details',
|
||||
' "disable-color-correct-rendering": true',
|
||||
' // "disable-hardware-acceleration": true',
|
||||
'}'
|
||||
];
|
||||
|
||||
|
|
|
@ -1734,50 +1734,87 @@ export function computeClippingRect(elementOrRect: HTMLElement | DOMRectReadOnly
|
|||
return { top, right, bottom, left };
|
||||
}
|
||||
|
||||
interface DomNodeAttributes {
|
||||
role?: string;
|
||||
ariaHidden?: boolean;
|
||||
style?: StyleAttributes;
|
||||
}
|
||||
type HTMLElementAttributeKeys<T> = Partial<{ [K in keyof T]: T[K] extends Function ? never : T[K] extends object ? HTMLElementAttributeKeys<T[K]> : T[K] }>;
|
||||
type ElementAttributes<T> = HTMLElementAttributeKeys<T> & Record<string, any>;
|
||||
type RemoveHTMLElement<T> = T extends HTMLElement ? never : T;
|
||||
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
|
||||
type ArrayToObj<T extends readonly any[]> = UnionToIntersection<RemoveHTMLElement<T[number]>>;
|
||||
type HHTMLElementTagNameMap = HTMLElementTagNameMap & { '': HTMLDivElement };
|
||||
|
||||
interface StyleAttributes {
|
||||
height?: number | string;
|
||||
width?: number | string;
|
||||
}
|
||||
type TagToElement<T> = T extends `${infer TStart}#${string}`
|
||||
? TStart extends keyof HHTMLElementTagNameMap
|
||||
? HHTMLElementTagNameMap[TStart]
|
||||
: HTMLElement
|
||||
: T extends `${infer TStart}.${string}`
|
||||
? TStart extends keyof HHTMLElementTagNameMap
|
||||
? HHTMLElementTagNameMap[TStart]
|
||||
: HTMLElement
|
||||
: T extends keyof HTMLElementTagNameMap
|
||||
? HTMLElementTagNameMap[T]
|
||||
: HTMLElement;
|
||||
|
||||
//<div role="presentation" aria-hidden="true" class="scroll-decoration"></div>
|
||||
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, TId extends string>(
|
||||
tag: TTag,
|
||||
attributes: { $: TId } & DomNodeAttributes
|
||||
): Record<TId | 'root', TagToElement<TTag>>;
|
||||
export function h<TTag extends string>(tag: TTag, attributes: DomNodeAttributes): Record<'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, TId extends string, T extends (HTMLElement | string | Record<string, HTMLElement>)[]>(
|
||||
tag: TTag,
|
||||
attributes: { $: TId } & DomNodeAttributes,
|
||||
children: T
|
||||
): (ArrayToObj<T> & Record<TId, TagToElement<TTag>>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never;
|
||||
export function h(tag: string, ...args: [] | [attributes: { $: string } & DomNodeAttributes | Record<string, any>, children?: any[]] | [children: any[]]): Record<string, HTMLElement> {
|
||||
let attributes: { $?: string } & DomNodeAttributes;
|
||||
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;
|
||||
|
||||
if (Array.isArray(args[0])) {
|
||||
|
@ -1788,14 +1825,29 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } & DomNod
|
|||
children = args[1];
|
||||
}
|
||||
|
||||
const [tagName, className] = tag.split('.');
|
||||
const match = H_REGEX.exec(tag);
|
||||
|
||||
if (!match || !match.groups) {
|
||||
throw new Error('Bad use of h');
|
||||
}
|
||||
|
||||
const tagName = match.groups['tag'] || 'div';
|
||||
const el = document.createElement(tagName);
|
||||
if (className) {
|
||||
el.className = className;
|
||||
|
||||
if (match.groups['id']) {
|
||||
el.id = match.groups['id'];
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -1810,10 +1862,6 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } & DomNod
|
|||
}
|
||||
|
||||
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(
|
||||
|
@ -1834,24 +1882,3 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } & DomNod
|
|||
function camelCaseToHyphenCase(str: string) {
|
||||
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
||||
}
|
||||
|
||||
type RemoveHTMLElement<T> = T extends HTMLElement ? never : T;
|
||||
|
||||
type ArrayToObj<T extends any[]> = UnionToIntersection<RemoveHTMLElement<T[number]>>;
|
||||
|
||||
|
||||
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
|
||||
|
||||
type HTMLElementsByTagName = {
|
||||
div: HTMLDivElement;
|
||||
span: HTMLSpanElement;
|
||||
a: HTMLAnchorElement;
|
||||
};
|
||||
|
||||
type TagToElement<T> = T extends `${infer TStart}.${string}`
|
||||
? TStart extends keyof HTMLElementsByTagName
|
||||
? HTMLElementsByTagName[TStart]
|
||||
: HTMLElement
|
||||
: T extends keyof HTMLElementsByTagName
|
||||
? HTMLElementsByTagName[T]
|
||||
: HTMLElement;
|
||||
|
|
|
@ -8,7 +8,7 @@ import { IMouseEvent } from 'vs/base/browser/mouseEvent';
|
|||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface IContentActionHandler {
|
||||
callback: (content: string, event?: IMouseEvent) => void;
|
||||
callback: (content: string, event: IMouseEvent) => void;
|
||||
readonly disposables: DisposableStore;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ export interface IBaseActionViewItemOptions {
|
|||
draggable?: boolean;
|
||||
isMenu?: boolean;
|
||||
useEventAsContext?: boolean;
|
||||
hoverDelegate?: IHoverDelegate;
|
||||
}
|
||||
|
||||
export class BaseActionViewItem extends Disposable implements IActionViewItem {
|
||||
|
@ -32,6 +33,8 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem {
|
|||
_context: unknown;
|
||||
readonly _action: IAction;
|
||||
|
||||
private customHover?: ICustomHover;
|
||||
|
||||
get action() {
|
||||
return this._action;
|
||||
}
|
||||
|
@ -210,8 +213,27 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem {
|
|||
// implement in subclass
|
||||
}
|
||||
|
||||
protected getTooltip(): string | undefined {
|
||||
return this.getAction().tooltip;
|
||||
}
|
||||
|
||||
protected updateTooltip(): void {
|
||||
// implement in subclass
|
||||
if (!this.element) {
|
||||
return;
|
||||
}
|
||||
const title = this.getTooltip() ?? '';
|
||||
this.element.setAttribute('aria-label', title);
|
||||
if (!this.options.hoverDelegate) {
|
||||
this.element.title = title;
|
||||
} else {
|
||||
this.element.title = '';
|
||||
if (!this.customHover) {
|
||||
this.customHover = setupCustomHover(this.options.hoverDelegate, this.element, title);
|
||||
this._store.add(this.customHover);
|
||||
} else {
|
||||
this.customHover.update(title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected updateClass(): void {
|
||||
|
@ -236,7 +258,6 @@ export interface IActionViewItemOptions extends IBaseActionViewItemOptions {
|
|||
icon?: boolean;
|
||||
label?: boolean;
|
||||
keybinding?: string | null;
|
||||
hoverDelegate?: IHoverDelegate;
|
||||
}
|
||||
|
||||
export class ActionViewItem extends BaseActionViewItem {
|
||||
|
@ -245,7 +266,6 @@ export class ActionViewItem extends BaseActionViewItem {
|
|||
protected override options: IActionViewItemOptions;
|
||||
|
||||
private cssClass?: string;
|
||||
private customHover?: ICustomHover;
|
||||
|
||||
constructor(context: unknown, action: IAction, options: IActionViewItemOptions = {}) {
|
||||
super(context, action, options);
|
||||
|
@ -317,7 +337,7 @@ export class ActionViewItem extends BaseActionViewItem {
|
|||
}
|
||||
}
|
||||
|
||||
override updateTooltip(): void {
|
||||
override getTooltip() {
|
||||
let title: string | null = null;
|
||||
|
||||
if (this.getAction().tooltip) {
|
||||
|
@ -330,24 +350,7 @@ export class ActionViewItem extends BaseActionViewItem {
|
|||
title = nls.localize({ key: 'titleLabel', comment: ['action title', 'action keybinding'] }, "{0} ({1})", title, this.options.keybinding);
|
||||
}
|
||||
}
|
||||
this._applyUpdateTooltip(title);
|
||||
}
|
||||
|
||||
protected _applyUpdateTooltip(title: string | undefined | null): void {
|
||||
if (title && this.label) {
|
||||
this.label.setAttribute('aria-label', title);
|
||||
if (!this.options.hoverDelegate) {
|
||||
this.label.title = title;
|
||||
} else {
|
||||
this.label.title = '';
|
||||
if (!this.customHover) {
|
||||
this.customHover = setupCustomHover(this.options.hoverDelegate, this.label, title);
|
||||
this._store.add(this.customHover);
|
||||
} else {
|
||||
this.customHover.update(title);
|
||||
}
|
||||
}
|
||||
}
|
||||
return title ?? undefined;
|
||||
}
|
||||
|
||||
override updateClass(): void {
|
||||
|
|
|
@ -46,8 +46,10 @@
|
|||
outline-offset: -1px !important;
|
||||
}
|
||||
|
||||
.monaco-button-dropdown.disabled .monaco-button-dropdown-separator {
|
||||
opacity: 0.4;
|
||||
.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 !important;
|
||||
}
|
||||
|
||||
.monaco-button-dropdown .monaco-button-dropdown-separator {
|
||||
|
|
|
@ -28,6 +28,7 @@ export interface IButtonStyles {
|
|||
buttonBackground?: Color;
|
||||
buttonHoverBackground?: Color;
|
||||
buttonForeground?: Color;
|
||||
buttonSeparator?: Color;
|
||||
buttonSecondaryBackground?: Color;
|
||||
buttonSecondaryHoverBackground?: Color;
|
||||
buttonSecondaryForeground?: Color;
|
||||
|
@ -37,6 +38,7 @@ export interface IButtonStyles {
|
|||
const defaultOptions: IButtonStyles = {
|
||||
buttonBackground: Color.fromHex('#0E639C'),
|
||||
buttonHoverBackground: Color.fromHex('#006BB3'),
|
||||
buttonSeparator: Color.white,
|
||||
buttonForeground: Color.white
|
||||
};
|
||||
|
||||
|
@ -315,7 +317,7 @@ export class ButtonWithDropdown extends Disposable implements IButton {
|
|||
|
||||
// Separator
|
||||
this.separatorContainer.style.backgroundColor = styles.buttonBackground?.toString() ?? '';
|
||||
this.separator.style.backgroundColor = styles.buttonForeground?.toString() ?? '';
|
||||
this.separator.style.backgroundColor = styles.buttonSeparator?.toString() ?? '';
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
|
@ -339,12 +341,10 @@ export class ButtonWithDescription extends Button implements IButtonWithDescript
|
|||
|
||||
this._labelElement = document.createElement('div');
|
||||
this._labelElement.classList.add('monaco-button-label');
|
||||
this._labelElement.tabIndex = -1;
|
||||
this._element.appendChild(this._labelElement);
|
||||
|
||||
this._descriptionElement = document.createElement('div');
|
||||
this._descriptionElement.classList.add('monaco-button-description');
|
||||
this._descriptionElement.tabIndex = -1;
|
||||
this._element.appendChild(this._descriptionElement);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
.context-view {
|
||||
position: absolute;
|
||||
z-index: 2500;
|
||||
}
|
||||
|
||||
.context-view.fixed {
|
||||
|
@ -13,6 +12,5 @@
|
|||
font-family: inherit;
|
||||
font-size: 13px;
|
||||
position: fixed;
|
||||
z-index: 2500;
|
||||
color: inherit;
|
||||
}
|
||||
|
|
|
@ -206,7 +206,7 @@ export class ContextView extends Disposable {
|
|||
this.view.className = 'context-view';
|
||||
this.view.style.top = '0px';
|
||||
this.view.style.left = '0px';
|
||||
this.view.style.zIndex = '2500';
|
||||
this.view.style.zIndex = '2575';
|
||||
this.view.style.position = this.useFixedPosition ? 'fixed' : 'absolute';
|
||||
DOM.show(this.view);
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import * as dom from 'vs/base/browser/dom';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { IToggleStyles } from 'vs/base/browser/ui/toggle/toggle';
|
||||
import { IToggleStyles, Toggle } from 'vs/base/browser/ui/toggle/toggle';
|
||||
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { CaseSensitiveToggle, RegexToggle, WholeWordsToggle } from 'vs/base/browser/ui/findinput/findInputToggles';
|
||||
import { HistoryInputBox, IInputBoxStyles, IInputValidator, IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
|
@ -31,6 +31,7 @@ export interface IFindInputOptions extends IFindInputStyles {
|
|||
readonly appendWholeWordsLabel?: string;
|
||||
readonly appendRegexLabel?: string;
|
||||
readonly history?: string[];
|
||||
readonly additionalToggles?: Toggle[];
|
||||
readonly showHistoryHint?: () => boolean;
|
||||
}
|
||||
|
||||
|
@ -74,6 +75,7 @@ export class FindInput extends Widget {
|
|||
protected regex: RegexToggle;
|
||||
protected wholeWords: WholeWordsToggle;
|
||||
protected caseSensitive: CaseSensitiveToggle;
|
||||
protected additionalToggles: Toggle[] = [];
|
||||
public domNode: HTMLElement;
|
||||
public inputBox: HistoryInputBox;
|
||||
|
||||
|
@ -209,10 +211,6 @@ export class FindInput extends Widget {
|
|||
this._onCaseSensitiveKeyDown.fire(e);
|
||||
}));
|
||||
|
||||
if (this._showOptionButtons) {
|
||||
this.inputBox.paddingRight = this.caseSensitive.width() + this.wholeWords.width() + this.regex.width();
|
||||
}
|
||||
|
||||
// Arrow-Key support to navigate between options
|
||||
const indexes = [this.caseSensitive.domNode, this.wholeWords.domNode, this.regex.domNode];
|
||||
this.onkeydown(this.domNode, (event: IKeyboardEvent) => {
|
||||
|
@ -250,6 +248,34 @@ export class FindInput extends Widget {
|
|||
this.controls.appendChild(this.wholeWords.domNode);
|
||||
this.controls.appendChild(this.regex.domNode);
|
||||
|
||||
if (!this._showOptionButtons) {
|
||||
this.caseSensitive.domNode.style.display = 'none';
|
||||
this.wholeWords.domNode.style.display = 'none';
|
||||
this.regex.domNode.style.display = 'none';
|
||||
}
|
||||
|
||||
for (const toggle of options?.additionalToggles ?? []) {
|
||||
this._register(toggle);
|
||||
this.controls.appendChild(toggle.domNode);
|
||||
|
||||
this._register(toggle.onChange(viaKeyboard => {
|
||||
this._onDidOptionChange.fire(viaKeyboard);
|
||||
if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) {
|
||||
this.inputBox.focus();
|
||||
}
|
||||
}));
|
||||
|
||||
this.additionalToggles.push(toggle);
|
||||
}
|
||||
|
||||
if (this.additionalToggles.length > 0) {
|
||||
this.controls.style.display = 'block';
|
||||
}
|
||||
|
||||
this.inputBox.paddingRight =
|
||||
(this._showOptionButtons ? this.caseSensitive.width() + this.wholeWords.width() + this.regex.width() : 0)
|
||||
+ this.additionalToggles.reduce((r, t) => r + t.width(), 0);
|
||||
|
||||
this.domNode.appendChild(this.controls);
|
||||
|
||||
parent?.appendChild(this.domNode);
|
||||
|
@ -282,6 +308,10 @@ export class FindInput extends Widget {
|
|||
this.regex.enable();
|
||||
this.wholeWords.enable();
|
||||
this.caseSensitive.enable();
|
||||
|
||||
for (const toggle of this.additionalToggles) {
|
||||
toggle.enable();
|
||||
}
|
||||
}
|
||||
|
||||
public disable(): void {
|
||||
|
@ -290,6 +320,10 @@ export class FindInput extends Widget {
|
|||
this.regex.disable();
|
||||
this.wholeWords.disable();
|
||||
this.caseSensitive.disable();
|
||||
|
||||
for (const toggle of this.additionalToggles) {
|
||||
toggle.disable();
|
||||
}
|
||||
}
|
||||
|
||||
public setFocusInputOnOptionClick(value: boolean): void {
|
||||
|
@ -356,6 +390,10 @@ export class FindInput extends Widget {
|
|||
this.wholeWords.style(toggleStyles);
|
||||
this.caseSensitive.style(toggleStyles);
|
||||
|
||||
for (const toggle of this.additionalToggles) {
|
||||
toggle.style(toggleStyles);
|
||||
}
|
||||
|
||||
const inputBoxStyles: IInputBoxStyles = {
|
||||
inputBackground: this.inputBackground,
|
||||
inputForeground: this.inputForeground,
|
||||
|
|
|
@ -65,72 +65,7 @@
|
|||
z-index: 1000;
|
||||
}
|
||||
|
||||
/* Type filter */
|
||||
|
||||
.monaco-list-type-filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
border-radius: 2px;
|
||||
padding: 0px 3px;
|
||||
max-width: calc(100% - 10px);
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-align: right;
|
||||
box-sizing: border-box;
|
||||
cursor: all-scroll;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
height: 20px;
|
||||
z-index: 1;
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
.monaco-list-type-filter.dragging {
|
||||
transition: top 0.2s, left 0.2s;
|
||||
}
|
||||
|
||||
.monaco-list-type-filter.ne {
|
||||
right: 4px;
|
||||
}
|
||||
|
||||
.monaco-list-type-filter.nw {
|
||||
left: 4px;
|
||||
}
|
||||
|
||||
.monaco-list-type-filter > .controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
transition: width 0.2s;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.monaco-list-type-filter.dragging > .controls,
|
||||
.monaco-list-type-filter:hover > .controls {
|
||||
width: 36px;
|
||||
}
|
||||
|
||||
.monaco-list-type-filter > .controls > * {
|
||||
border: none;
|
||||
box-sizing: border-box;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
background: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.monaco-list-type-filter > .controls > .filter {
|
||||
margin-left: 4px;
|
||||
}
|
||||
/* Filter */
|
||||
|
||||
.monaco-list-type-filter-message {
|
||||
position: absolute;
|
||||
|
@ -149,13 +84,3 @@
|
|||
.monaco-list-type-filter-message:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Electron */
|
||||
|
||||
.monaco-list-type-filter {
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.monaco-list-type-filter.dragging {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
|||
import { IThemable } from 'vs/base/common/styler';
|
||||
import 'vs/css!./list';
|
||||
import { IListContextMenuEvent, IListEvent, IListMouseEvent, IListRenderer, IListVirtualDelegate } from './list';
|
||||
import { IListAccessibilityProvider, IListOptions, IListOptionsUpdate, IListStyles, List } from './listWidget';
|
||||
import { IListAccessibilityProvider, IListOptions, IListOptionsUpdate, IListStyles, List, TypeNavigationMode } from './listWidget';
|
||||
|
||||
export interface IPagedRenderer<TElement, TTemplateData> extends IListRenderer<TElement, TTemplateData> {
|
||||
renderPlaceholder(index: number, templateData: TTemplateData): void;
|
||||
|
@ -95,8 +95,8 @@ class PagedAccessibilityProvider<T> implements IListAccessibilityProvider<number
|
|||
}
|
||||
|
||||
export interface IPagedListOptions<T> {
|
||||
readonly enableKeyboardNavigation?: boolean;
|
||||
readonly automaticKeyboardNavigation?: boolean;
|
||||
readonly typeNavigationEnabled?: boolean;
|
||||
readonly typeNavigationMode?: TypeNavigationMode;
|
||||
readonly ariaLabel?: string;
|
||||
readonly keyboardSupport?: boolean;
|
||||
readonly multipleSelectionSupport?: boolean;
|
||||
|
@ -282,8 +282,8 @@ export class PagedList<T> implements IThemable, IDisposable {
|
|||
this.list.layout(height, width);
|
||||
}
|
||||
|
||||
toggleKeyboardNavigation(): void {
|
||||
this.list.toggleKeyboardNavigation();
|
||||
triggerTypeNavigation(): void {
|
||||
this.list.triggerTypeNavigation();
|
||||
}
|
||||
|
||||
reveal(index: number, relativeTop?: number): void {
|
||||
|
|
|
@ -9,6 +9,7 @@ import { DomEmitter, stopEvent } from 'vs/base/browser/event';
|
|||
import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { Gesture } from 'vs/base/browser/touch';
|
||||
import { alert } from 'vs/base/browser/ui/aria/aria';
|
||||
import { IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput';
|
||||
import { CombinedSpliceable } from 'vs/base/browser/ui/list/splice';
|
||||
import { ScrollableElementChangeOptions } from 'vs/base/browser/ui/scrollbar/scrollableElementOptions';
|
||||
import { binarySearch, firstOrDefault, range } from 'vs/base/common/arrays';
|
||||
|
@ -384,7 +385,12 @@ class KeyboardController<T> implements IDisposable {
|
|||
}
|
||||
}
|
||||
|
||||
enum TypeLabelControllerState {
|
||||
export enum TypeNavigationMode {
|
||||
Automatic,
|
||||
Trigger
|
||||
}
|
||||
|
||||
enum TypeNavigationControllerState {
|
||||
Idle,
|
||||
Typing
|
||||
}
|
||||
|
@ -402,12 +408,12 @@ export const DefaultKeyboardNavigationDelegate = new class implements IKeyboardN
|
|||
}
|
||||
};
|
||||
|
||||
class TypeLabelController<T> implements IDisposable {
|
||||
class TypeNavigationController<T> implements IDisposable {
|
||||
|
||||
private enabled = false;
|
||||
private state: TypeLabelControllerState = TypeLabelControllerState.Idle;
|
||||
private state: TypeNavigationControllerState = TypeNavigationControllerState.Idle;
|
||||
|
||||
private automaticKeyboardNavigation = true;
|
||||
private mode = TypeNavigationMode.Automatic;
|
||||
private triggered = false;
|
||||
private previouslyFocused = -1;
|
||||
|
||||
|
@ -424,20 +430,16 @@ class TypeLabelController<T> implements IDisposable {
|
|||
}
|
||||
|
||||
updateOptions(options: IListOptions<T>): void {
|
||||
const enableKeyboardNavigation = typeof options.enableKeyboardNavigation === 'undefined' ? true : !!options.enableKeyboardNavigation;
|
||||
|
||||
if (enableKeyboardNavigation) {
|
||||
if (options.typeNavigationEnabled ?? true) {
|
||||
this.enable();
|
||||
} else {
|
||||
this.disable();
|
||||
}
|
||||
|
||||
if (typeof options.automaticKeyboardNavigation !== 'undefined') {
|
||||
this.automaticKeyboardNavigation = options.automaticKeyboardNavigation;
|
||||
}
|
||||
this.mode = options.typeNavigationMode ?? TypeNavigationMode.Automatic;
|
||||
}
|
||||
|
||||
toggle(): void {
|
||||
trigger(): void {
|
||||
this.triggered = !this.triggered;
|
||||
}
|
||||
|
||||
|
@ -448,10 +450,10 @@ class TypeLabelController<T> implements IDisposable {
|
|||
|
||||
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.automaticKeyboardNavigation || this.triggered)
|
||||
.filter(() => this.mode === TypeNavigationMode.Automatic || this.triggered)
|
||||
.map(event => new StandardKeyboardEvent(event))
|
||||
.filter(e => this.delegate.mightProducePrintableCharacter(e))
|
||||
.forEach(e => e.preventDefault())
|
||||
.forEach(e => { e.preventDefault(); e.stopPropagation(); })
|
||||
.map(event => event.browserEvent.key)
|
||||
.event;
|
||||
|
||||
|
@ -490,15 +492,15 @@ class TypeLabelController<T> implements IDisposable {
|
|||
|
||||
private onInput(word: string | null): void {
|
||||
if (!word) {
|
||||
this.state = TypeLabelControllerState.Idle;
|
||||
this.state = TypeNavigationControllerState.Idle;
|
||||
this.triggered = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const focus = this.list.getFocus();
|
||||
const start = focus.length > 0 ? focus[0] : 0;
|
||||
const delta = this.state === TypeLabelControllerState.Idle ? 1 : 0;
|
||||
this.state = TypeLabelControllerState.Typing;
|
||||
const delta = this.state === TypeNavigationControllerState.Idle ? 1 : 0;
|
||||
this.state = TypeNavigationControllerState.Typing;
|
||||
|
||||
for (let i = 0; i < this.list.length; i++) {
|
||||
const index = (start + i + delta) % this.list.length;
|
||||
|
@ -895,22 +897,6 @@ export class DefaultStyleController implements IStyleController {
|
|||
`);
|
||||
}
|
||||
|
||||
if (styles.listFilterWidgetBackground) {
|
||||
content.push(`.monaco-list-type-filter { background-color: ${styles.listFilterWidgetBackground} }`);
|
||||
}
|
||||
|
||||
if (styles.listFilterWidgetOutline) {
|
||||
content.push(`.monaco-list-type-filter { border: 1px solid ${styles.listFilterWidgetOutline}; }`);
|
||||
}
|
||||
|
||||
if (styles.listFilterWidgetNoMatchesOutline) {
|
||||
content.push(`.monaco-list-type-filter.no-matches { border: 1px solid ${styles.listFilterWidgetNoMatchesOutline}; }`);
|
||||
}
|
||||
|
||||
if (styles.listMatchesShadow) {
|
||||
content.push(`.monaco-list-type-filter { box-shadow: 1px 1px 1px ${styles.listMatchesShadow}; }`);
|
||||
}
|
||||
|
||||
if (styles.tableColumnsBorder) {
|
||||
content.push(`
|
||||
.monaco-table:hover > .monaco-split-view2,
|
||||
|
@ -934,8 +920,8 @@ export class DefaultStyleController implements IStyleController {
|
|||
}
|
||||
|
||||
export interface IListOptionsUpdate extends IListViewOptionsUpdate {
|
||||
readonly enableKeyboardNavigation?: boolean;
|
||||
readonly automaticKeyboardNavigation?: boolean;
|
||||
readonly typeNavigationEnabled?: boolean;
|
||||
readonly typeNavigationMode?: TypeNavigationMode;
|
||||
readonly multipleSelectionSupport?: boolean;
|
||||
}
|
||||
|
||||
|
@ -964,7 +950,7 @@ export interface IListOptions<T> extends IListOptionsUpdate {
|
|||
readonly alwaysConsumeMouseWheel?: boolean;
|
||||
}
|
||||
|
||||
export interface IListStyles {
|
||||
export interface IListStyles extends IFindInputStyles {
|
||||
listBackground?: Color;
|
||||
listFocusBackground?: Color;
|
||||
listFocusForeground?: Color;
|
||||
|
@ -989,7 +975,7 @@ export interface IListStyles {
|
|||
listFilterWidgetBackground?: Color;
|
||||
listFilterWidgetOutline?: Color;
|
||||
listFilterWidgetNoMatchesOutline?: Color;
|
||||
listMatchesShadow?: Color;
|
||||
listFilterWidgetShadow?: Color;
|
||||
treeIndentGuidesStroke?: Color;
|
||||
tableColumnsBorder?: Color;
|
||||
tableOddRowsBackgroundColor?: Color;
|
||||
|
@ -1247,7 +1233,7 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
|
|||
protected view: ListView<T>;
|
||||
private spliceable: ISpliceable<T>;
|
||||
private styleController: IStyleController;
|
||||
private typeLabelController?: TypeLabelController<T>;
|
||||
private typeNavigationController?: TypeNavigationController<T>;
|
||||
private accessibilityProvider?: IListAccessibilityProvider<T>;
|
||||
private keyboardController: KeyboardController<T> | undefined;
|
||||
private mouseController: MouseController<T>;
|
||||
|
@ -1387,8 +1373,8 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
|
|||
|
||||
if (_options.keyboardNavigationLabelProvider) {
|
||||
const delegate = _options.keyboardNavigationDelegate || DefaultKeyboardNavigationDelegate;
|
||||
this.typeLabelController = new TypeLabelController(this, this.view, _options.keyboardNavigationLabelProvider, delegate);
|
||||
this.disposables.add(this.typeLabelController);
|
||||
this.typeNavigationController = new TypeNavigationController(this, this.view, _options.keyboardNavigationLabelProvider, delegate);
|
||||
this.disposables.add(this.typeNavigationController);
|
||||
}
|
||||
|
||||
this.mouseController = this.createMouseController(_options);
|
||||
|
@ -1413,7 +1399,7 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
|
|||
updateOptions(optionsUpdate: IListOptionsUpdate = {}): void {
|
||||
this._options = { ...this._options, ...optionsUpdate };
|
||||
|
||||
this.typeLabelController?.updateOptions(this._options);
|
||||
this.typeNavigationController?.updateOptions(this._options);
|
||||
|
||||
if (this._options.multipleSelectionController !== undefined) {
|
||||
if (this._options.multipleSelectionSupport) {
|
||||
|
@ -1529,9 +1515,9 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
|
|||
this.view.layout(height, width);
|
||||
}
|
||||
|
||||
toggleKeyboardNavigation(): void {
|
||||
if (this.typeLabelController) {
|
||||
this.typeLabelController.toggle();
|
||||
triggerTypeNavigation(): void {
|
||||
if (this.typeNavigationController) {
|
||||
this.typeNavigationController.trigger();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,8 +33,7 @@ export const MENU_ESCAPED_MNEMONIC_REGEX = /(&)?(&)([^\s&])/g;
|
|||
|
||||
export enum Direction {
|
||||
Right,
|
||||
Left,
|
||||
Down
|
||||
Left
|
||||
}
|
||||
|
||||
export interface IMenuOptions {
|
||||
|
|
|
@ -13,6 +13,10 @@
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
.menubar.overflow-menu-only {
|
||||
width: 38px;
|
||||
}
|
||||
|
||||
.fullscreen .menubar:not(.compact) {
|
||||
margin: 0px;
|
||||
padding: 4px 5px;
|
||||
|
@ -93,6 +97,7 @@
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
.menubar:not(.compact) .menubar-menu-button:first-child .toolbar-toggle-more::before,
|
||||
.menubar.compact .toolbar-toggle-more::before {
|
||||
content: "\eb94" !important;
|
||||
}
|
||||
|
|
|
@ -334,8 +334,6 @@ export class MenuBar extends Disposable {
|
|||
triggerKeys.push(KeyCode.RightArrow);
|
||||
} else if (this.options.compactMode === Direction.Left) {
|
||||
triggerKeys.push(KeyCode.LeftArrow);
|
||||
} else if (this.options.compactMode === Direction.Down) {
|
||||
triggerKeys.push(KeyCode.DownArrow);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -475,6 +473,11 @@ export class MenuBar extends Disposable {
|
|||
return;
|
||||
}
|
||||
|
||||
const overflowMenuOnlyClass = 'overflow-menu-only';
|
||||
|
||||
// Remove overflow only restriction to allow the most space
|
||||
this.container.classList.toggle(overflowMenuOnlyClass, false);
|
||||
|
||||
const sizeAvailable = this.container.offsetWidth;
|
||||
let currentSize = 0;
|
||||
let full = this.isCompact;
|
||||
|
@ -501,6 +504,18 @@ export class MenuBar extends Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// If below minimium menu threshold, show the overflow menu only as hamburger menu
|
||||
if (this.numMenusShown - 1 <= showableMenus.length / 2) {
|
||||
for (const menuBarMenu of showableMenus) {
|
||||
menuBarMenu.buttonElement.style.visibility = 'hidden';
|
||||
}
|
||||
|
||||
full = true;
|
||||
this.numMenusShown = 0;
|
||||
currentSize = 0;
|
||||
}
|
||||
|
||||
// Overflow
|
||||
if (this.isCompact) {
|
||||
this.overflowMenu.actions = [];
|
||||
|
@ -540,6 +555,9 @@ export class MenuBar extends Disposable {
|
|||
this.container.appendChild(this.overflowMenu.buttonElement);
|
||||
this.overflowMenu.buttonElement.style.visibility = 'hidden';
|
||||
}
|
||||
|
||||
// If we are only showing the overflow, add this class to avoid taking up space
|
||||
this.container.classList.toggle(overflowMenuOnlyClass, this.numMenusShown === 0);
|
||||
}
|
||||
|
||||
private updateLabels(titleElement: HTMLElement, buttonElement: HTMLElement, label: string): void {
|
||||
|
|
|
@ -265,8 +265,8 @@ export class Table<TRow> implements ISpliceable<TRow>, IThemable, IDisposable {
|
|||
this.list.layout(listHeight, width);
|
||||
}
|
||||
|
||||
toggleKeyboardNavigation(): void {
|
||||
this.list.toggleKeyboardNavigation();
|
||||
triggerTypeNavigation(): void {
|
||||
this.list.triggerTypeNavigation();
|
||||
}
|
||||
|
||||
style(styles: ITableStyles): void {
|
||||
|
|
|
@ -148,6 +148,7 @@ export class Toggle extends Widget {
|
|||
this.checked = !this._checked;
|
||||
this._onChange.fire(true);
|
||||
keyboardEvent.preventDefault();
|
||||
keyboardEvent.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,27 +3,34 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DragAndDropData, IDragAndDropData, StaticDND } from 'vs/base/browser/dnd';
|
||||
import { $, addDisposableListener, append, clearNode, createStyleSheet, getDomNodePagePosition, hasParentWithClass } from 'vs/base/browser/dom';
|
||||
import { IDragAndDropData } from 'vs/base/browser/dnd';
|
||||
import { $, append, clearNode, createStyleSheet, h, hasParentWithClass } from 'vs/base/browser/dom';
|
||||
import { DomEmitter } from 'vs/base/browser/event';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { IIdentityProvider, IKeyboardNavigationDelegate, IKeyboardNavigationLabelProvider, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IListMouseEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput';
|
||||
import { IMessage, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IListMouseEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
|
||||
import { DefaultKeyboardNavigationDelegate, IListOptions, IListStyles, isButton, isInputElement, isMonacoEditor, List, MouseController } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IListOptions, IListStyles, isButton, isInputElement, isMonacoEditor, List, MouseController, TypeNavigationMode } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { Toggle } from 'vs/base/browser/ui/toggle/toggle';
|
||||
import { getVisibleState, isFilterResult } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { ICollapseStateChangeEvent, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeEvent, ITreeFilter, ITreeModel, ITreeModelSpliceEvent, ITreeMouseEvent, ITreeNavigator, ITreeNode, ITreeRenderer, TreeDragOverBubble, TreeError, TreeFilterResult, TreeMouseEventTarget, TreeVisibility } from 'vs/base/browser/ui/tree/tree';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { distinct, equals, firstOrDefault, range } from 'vs/base/common/arrays';
|
||||
import { disposableTimeout } from 'vs/base/common/async';
|
||||
import { disposableTimeout, timeout } from 'vs/base/common/async';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { SetMap } from 'vs/base/common/collections';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { Emitter, Event, EventBufferer, Relay } from 'vs/base/common/event';
|
||||
import { fuzzyScore, FuzzyScore } from 'vs/base/common/filters';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { clamp } from 'vs/base/common/numbers';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { ScrollEvent } from 'vs/base/common/scrollable';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { isNumber } from 'vs/base/common/types';
|
||||
import 'vs/css!./media/tree';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
|
@ -194,8 +201,7 @@ function asListOptions<T, TFilterData, TRef>(modelProvider: () => ITreeModel<T,
|
|||
getKeyboardNavigationLabel(node) {
|
||||
return options.keyboardNavigationLabelProvider!.getKeyboardNavigationLabel(node.element);
|
||||
}
|
||||
},
|
||||
enableKeyboardNavigation: options.simpleKeyboardNavigation
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -563,7 +569,7 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer
|
|||
|
||||
export type LabelFuzzyScore = { label: string; score: FuzzyScore };
|
||||
|
||||
class TypeFilter<T> implements ITreeFilter<T, FuzzyScore | LabelFuzzyScore>, IDisposable {
|
||||
class FindFilter<T> implements ITreeFilter<T, FuzzyScore | LabelFuzzyScore>, IDisposable {
|
||||
private _totalCount = 0;
|
||||
get totalCount(): number { return this._totalCount; }
|
||||
private _matchCount = 0;
|
||||
|
@ -590,10 +596,6 @@ class TypeFilter<T> implements ITreeFilter<T, FuzzyScore | LabelFuzzyScore>, IDi
|
|||
if (this._filter) {
|
||||
const result = this._filter.filter(element, parentVisibility);
|
||||
|
||||
if (this.tree.options.simpleKeyboardNavigation) {
|
||||
return result;
|
||||
}
|
||||
|
||||
let visibility: TreeVisibility;
|
||||
|
||||
if (typeof result === 'boolean') {
|
||||
|
@ -611,7 +613,7 @@ class TypeFilter<T> implements ITreeFilter<T, FuzzyScore | LabelFuzzyScore>, IDi
|
|||
|
||||
this._totalCount++;
|
||||
|
||||
if (this.tree.options.simpleKeyboardNavigation || !this._pattern) {
|
||||
if (!this._pattern) {
|
||||
this._matchCount++;
|
||||
return { data: FuzzyScore.Default, visibility: true };
|
||||
}
|
||||
|
@ -634,7 +636,7 @@ class TypeFilter<T> implements ITreeFilter<T, FuzzyScore | LabelFuzzyScore>, IDi
|
|||
}
|
||||
}
|
||||
|
||||
if (this.tree.options.filterOnType) {
|
||||
if (this.tree.findMode === TreeFindMode.Filter) {
|
||||
return TreeVisibility.Recurse;
|
||||
} else {
|
||||
return { data: FuzzyScore.Default, visibility: true };
|
||||
|
@ -651,170 +653,259 @@ class TypeFilter<T> implements ITreeFilter<T, FuzzyScore | LabelFuzzyScore>, IDi
|
|||
}
|
||||
}
|
||||
|
||||
class TypeFilterController<T, TFilterData> implements IDisposable {
|
||||
export interface ICaseSensitiveToggleOpts {
|
||||
readonly isChecked: boolean;
|
||||
readonly inputActiveOptionBorder?: Color;
|
||||
readonly inputActiveOptionForeground?: Color;
|
||||
readonly inputActiveOptionBackground?: Color;
|
||||
}
|
||||
|
||||
private _enabled = false;
|
||||
get enabled(): boolean { return this._enabled; }
|
||||
export class ModeToggle extends Toggle {
|
||||
constructor(opts?: ICaseSensitiveToggleOpts) {
|
||||
super({
|
||||
icon: Codicon.filter,
|
||||
title: localize('filter', "Filter"),
|
||||
isChecked: opts?.isChecked ?? false,
|
||||
inputActiveOptionBorder: opts?.inputActiveOptionBorder,
|
||||
inputActiveOptionForeground: opts?.inputActiveOptionForeground,
|
||||
inputActiveOptionBackground: opts?.inputActiveOptionBackground
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export interface IFindWidgetStyles extends IFindInputStyles, IListStyles { }
|
||||
|
||||
export interface IFindWidgetOpts extends IFindWidgetStyles { }
|
||||
|
||||
export enum TreeFindMode {
|
||||
Highlight,
|
||||
Filter
|
||||
}
|
||||
|
||||
class FindWidget<T, TFilterData> extends Disposable {
|
||||
|
||||
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) {
|
||||
this.modeToggle.checked = mode === TreeFindMode.Filter;
|
||||
this.findInput.inputBox.setPlaceHolder(mode === TreeFindMode.Filter ? localize('type to filter', "Type to filter") : localize('type to search', "Type to search"));
|
||||
}
|
||||
|
||||
private readonly modeToggle: ModeToggle;
|
||||
private readonly findInput: FindInput;
|
||||
private readonly actionbar: ActionBar;
|
||||
private width = 0;
|
||||
private right = 4;
|
||||
|
||||
readonly _onDidDisable = new Emitter<void>();
|
||||
readonly onDidDisable = this._onDidDisable.event;
|
||||
readonly onDidChangeValue: Event<string>;
|
||||
readonly onDidChangeMode: Event<TreeFindMode>;
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
private tree: AbstractTree<T, TFilterData, any>,
|
||||
contextViewProvider: IContextViewProvider,
|
||||
mode: TreeFindMode,
|
||||
options?: IFindWidgetOpts
|
||||
) {
|
||||
super();
|
||||
|
||||
container.appendChild(this.elements.root);
|
||||
this._register(toDisposable(() => container.removeChild(this.elements.root)));
|
||||
|
||||
this.modeToggle = this._register(new ModeToggle({ ...options, isChecked: mode === TreeFindMode.Filter }));
|
||||
this.onDidChangeMode = Event.map(this.modeToggle.onChange, () => this.modeToggle.checked ? TreeFindMode.Filter : TreeFindMode.Highlight, this._store);
|
||||
|
||||
this.findInput = this._register(new FindInput(this.elements.findInput, contextViewProvider, false, {
|
||||
label: localize('type to search', "Type to search"),
|
||||
additionalToggles: [this.modeToggle]
|
||||
}));
|
||||
|
||||
this.actionbar = this._register(new ActionBar(this.elements.actionbar));
|
||||
this.mode = mode;
|
||||
|
||||
const emitter = this._register(new DomEmitter(this.findInput.inputBox.inputElement, 'keydown'));
|
||||
const onKeyDown = this._register(Event.chain(emitter.event))
|
||||
.map(e => new StandardKeyboardEvent(e))
|
||||
.event;
|
||||
|
||||
this._register(onKeyDown((e): any => {
|
||||
switch (e.keyCode) {
|
||||
case KeyCode.DownArrow:
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.tree.domFocus();
|
||||
return;
|
||||
}
|
||||
}));
|
||||
|
||||
const closeAction = this._register(new Action('close', localize('close', "Close"), 'codicon codicon-close', true, () => this.dispose()));
|
||||
this.actionbar.push(closeAction, { icon: true, label: false });
|
||||
|
||||
const onGrabMouseDown = this._register(new DomEmitter(this.elements.grab, 'mousedown'));
|
||||
|
||||
this._register(onGrabMouseDown.event(e => {
|
||||
const disposables = new DisposableStore();
|
||||
const onWindowMouseMove = disposables.add(new DomEmitter(window, 'mousemove'));
|
||||
const onWindowMouseUp = disposables.add(new DomEmitter(window, 'mouseup'));
|
||||
|
||||
const startRight = this.right;
|
||||
const startX = e.pageX;
|
||||
this.elements.grab.classList.add('grabbing');
|
||||
|
||||
const update = (e: MouseEvent) => {
|
||||
const deltaX = e.pageX - startX;
|
||||
this.right = startRight - deltaX;
|
||||
this.layout();
|
||||
};
|
||||
|
||||
disposables.add(onWindowMouseMove.event(update));
|
||||
disposables.add(onWindowMouseUp.event(e => {
|
||||
update(e);
|
||||
this.elements.grab.classList.remove('grabbing');
|
||||
disposables.dispose();
|
||||
}));
|
||||
}));
|
||||
|
||||
|
||||
this.onDidChangeValue = this.findInput.onDidChange;
|
||||
this.style(options ?? {});
|
||||
}
|
||||
|
||||
style(styles: IFindWidgetStyles): void {
|
||||
this.findInput.style(styles);
|
||||
|
||||
if (styles.listFilterWidgetBackground) {
|
||||
this.elements.root.style.backgroundColor = styles.listFilterWidgetBackground.toString();
|
||||
}
|
||||
|
||||
if (styles.listFilterWidgetShadow) {
|
||||
this.elements.root.style.boxShadow = `0 0 8px 2px ${styles.listFilterWidgetShadow}`;
|
||||
}
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.findInput.focus();
|
||||
}
|
||||
|
||||
select() {
|
||||
this.findInput.select();
|
||||
}
|
||||
|
||||
layout(width: number = this.width): void {
|
||||
this.width = width;
|
||||
this.right = Math.min(Math.max(20, this.right), Math.max(20, width - 170));
|
||||
this.elements.root.style.right = `${this.right}px`;
|
||||
}
|
||||
|
||||
showMessage(message: IMessage): void {
|
||||
this.findInput.showMessage(message);
|
||||
}
|
||||
|
||||
clearMessage(): void {
|
||||
this.findInput.clearMessage();
|
||||
}
|
||||
|
||||
override async dispose(): Promise<void> {
|
||||
this._onDidDisable.fire();
|
||||
this.elements.root.classList.add('disabled');
|
||||
await timeout(300);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class FindController<T, TFilterData> implements IDisposable {
|
||||
|
||||
private _pattern = '';
|
||||
get pattern(): string { return this._pattern; }
|
||||
|
||||
private _filterOnType: boolean;
|
||||
get filterOnType(): boolean { return this._filterOnType; }
|
||||
private _mode: TreeFindMode;
|
||||
get mode(): TreeFindMode { return this._mode; }
|
||||
set mode(mode: TreeFindMode) {
|
||||
if (mode === this._mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
private _empty: boolean = false;
|
||||
get empty(): boolean { return this._empty; }
|
||||
this._mode = mode;
|
||||
|
||||
private readonly _onDidChangeEmptyState = new Emitter<boolean>();
|
||||
readonly onDidChangeEmptyState: Event<boolean> = Event.latch(this._onDidChangeEmptyState.event);
|
||||
if (this.widget) {
|
||||
this.widget.mode = this._mode;
|
||||
}
|
||||
|
||||
private positionClassName = 'ne';
|
||||
private domNode: HTMLElement;
|
||||
private messageDomNode: HTMLElement;
|
||||
private labelDomNode: HTMLElement;
|
||||
private filterOnTypeDomNode: HTMLInputElement;
|
||||
private clearDomNode: HTMLElement;
|
||||
private keyboardNavigationEventFilter?: IKeyboardNavigationEventFilter;
|
||||
this.tree.refilter();
|
||||
this.render();
|
||||
this._onDidChangeMode.fire(mode);
|
||||
}
|
||||
|
||||
private automaticKeyboardNavigation = true;
|
||||
private triggered = false;
|
||||
private widget: FindWidget<T, TFilterData> | undefined;
|
||||
private styles: IFindWidgetStyles | undefined;
|
||||
private width = 0;
|
||||
|
||||
private readonly _onDidChangeMode = new Emitter<TreeFindMode>();
|
||||
readonly onDidChangeMode = this._onDidChangeMode.event;
|
||||
|
||||
private readonly _onDidChangePattern = new Emitter<string>();
|
||||
readonly onDidChangePattern = this._onDidChangePattern.event;
|
||||
|
||||
private readonly enabledDisposables = new DisposableStore();
|
||||
private readonly _onDidChangeOpenState = new Emitter<boolean>();
|
||||
readonly onDidChangeOpenState = this._onDidChangeOpenState.event;
|
||||
|
||||
private enabledDisposables = new DisposableStore();
|
||||
private readonly disposables = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
private tree: AbstractTree<T, TFilterData, any>,
|
||||
model: ITreeModel<T, TFilterData, any>,
|
||||
private view: List<ITreeNode<T, TFilterData>>,
|
||||
private filter: TypeFilter<T>,
|
||||
private keyboardNavigationDelegate: IKeyboardNavigationDelegate
|
||||
private filter: FindFilter<T>,
|
||||
private readonly contextViewProvider: IContextViewProvider
|
||||
) {
|
||||
this.domNode = $(`.monaco-list-type-filter.${this.positionClassName}`);
|
||||
this.domNode.draggable = true;
|
||||
this.disposables.add(addDisposableListener(this.domNode, 'dragstart', () => this.onDragStart()));
|
||||
|
||||
this.messageDomNode = append(view.getHTMLElement(), $(`.monaco-list-type-filter-message`));
|
||||
|
||||
this.labelDomNode = append(this.domNode, $('span.label'));
|
||||
const controls = append(this.domNode, $('.controls'));
|
||||
|
||||
this._filterOnType = !!tree.options.filterOnType;
|
||||
this.filterOnTypeDomNode = append(controls, $<HTMLInputElement>('input.filter'));
|
||||
this.filterOnTypeDomNode.type = 'checkbox';
|
||||
this.filterOnTypeDomNode.checked = this._filterOnType;
|
||||
this.filterOnTypeDomNode.tabIndex = -1;
|
||||
this.updateFilterOnTypeTitleAndIcon();
|
||||
this.disposables.add(addDisposableListener(this.filterOnTypeDomNode, 'input', () => this.onDidChangeFilterOnType()));
|
||||
|
||||
this.clearDomNode = append(controls, $<HTMLInputElement>('button.clear' + Codicon.treeFilterClear.cssSelector));
|
||||
this.clearDomNode.tabIndex = -1;
|
||||
this.clearDomNode.title = localize('clear', "Clear");
|
||||
|
||||
this.keyboardNavigationEventFilter = tree.options.keyboardNavigationEventFilter;
|
||||
|
||||
this._mode = tree.options.defaultFindMode ?? TreeFindMode.Highlight;
|
||||
model.onDidSplice(this.onDidSpliceModel, this, this.disposables);
|
||||
this.updateOptions(tree.options);
|
||||
}
|
||||
|
||||
updateOptions(options: IAbstractTreeOptions<T, TFilterData>): void {
|
||||
if (options.simpleKeyboardNavigation) {
|
||||
this.disable();
|
||||
} else {
|
||||
this.enable();
|
||||
}
|
||||
|
||||
if (typeof options.filterOnType !== 'undefined') {
|
||||
this._filterOnType = !!options.filterOnType;
|
||||
this.filterOnTypeDomNode.checked = this._filterOnType;
|
||||
this.updateFilterOnTypeTitleAndIcon();
|
||||
}
|
||||
|
||||
if (typeof options.automaticKeyboardNavigation !== 'undefined') {
|
||||
this.automaticKeyboardNavigation = options.automaticKeyboardNavigation;
|
||||
}
|
||||
|
||||
this.tree.refilter();
|
||||
this.render();
|
||||
|
||||
if (!this.automaticKeyboardNavigation) {
|
||||
this.onEventOrInput('');
|
||||
}
|
||||
}
|
||||
|
||||
toggle(): void {
|
||||
this.triggered = !this.triggered;
|
||||
|
||||
if (!this.triggered) {
|
||||
this.onEventOrInput('');
|
||||
}
|
||||
}
|
||||
|
||||
private enable(): void {
|
||||
if (this._enabled) {
|
||||
open(): void {
|
||||
if (this.widget) {
|
||||
this.widget.focus();
|
||||
this.widget.select();
|
||||
return;
|
||||
}
|
||||
|
||||
const onRawKeyDown = this.enabledDisposables.add(new DomEmitter(this.view.getHTMLElement(), 'keydown'));
|
||||
const onKeyDown = Event.chain(onRawKeyDown.event)
|
||||
.filter(e => !isInputElement(e.target as HTMLElement) || e.target === this.filterOnTypeDomNode)
|
||||
.filter(e => e.key !== 'Dead' && !/^Media/.test(e.key))
|
||||
.map(e => new StandardKeyboardEvent(e))
|
||||
.filter(this.keyboardNavigationEventFilter || (() => true))
|
||||
.filter(() => this.automaticKeyboardNavigation || this.triggered)
|
||||
.filter(e => (this.keyboardNavigationDelegate.mightProducePrintableCharacter(e) && !(e.keyCode === KeyCode.DownArrow || e.keyCode === KeyCode.UpArrow || e.keyCode === KeyCode.LeftArrow || e.keyCode === KeyCode.RightArrow)) || ((this.pattern.length > 0 || this.triggered) && ((e.keyCode === KeyCode.Escape || e.keyCode === KeyCode.Backspace) && !e.altKey && !e.ctrlKey && !e.metaKey) || (e.keyCode === KeyCode.Backspace && (isMacintosh ? (e.altKey && !e.metaKey) : e.ctrlKey) && !e.shiftKey)))
|
||||
.forEach(e => { e.stopPropagation(); e.preventDefault(); })
|
||||
.event;
|
||||
this.widget = new FindWidget(this.view.getHTMLElement(), this.tree, this.contextViewProvider, this._mode, this.styles);
|
||||
this.enabledDisposables.add(this.widget);
|
||||
|
||||
const onClearClick = this.enabledDisposables.add(new DomEmitter(this.clearDomNode, 'click'));
|
||||
this.widget.onDidChangeValue(this.onDidChangeValue, this, this.enabledDisposables);
|
||||
this.widget.onDidChangeMode(mode => this.mode = mode, undefined, this.enabledDisposables);
|
||||
this.widget.onDidDisable(this.close, this, this.enabledDisposables);
|
||||
|
||||
Event.chain(Event.any<MouseEvent | StandardKeyboardEvent>(onKeyDown, onClearClick.event))
|
||||
.event(this.onEventOrInput, this, this.enabledDisposables);
|
||||
this.widget.layout(this.width);
|
||||
this.widget.focus();
|
||||
|
||||
this.filter.pattern = '';
|
||||
this.tree.refilter();
|
||||
this.render();
|
||||
this._enabled = true;
|
||||
this.triggered = false;
|
||||
this._onDidChangeOpenState.fire(true);
|
||||
}
|
||||
|
||||
private disable(): void {
|
||||
if (!this._enabled) {
|
||||
close(): void {
|
||||
if (!this.widget) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.domNode.remove();
|
||||
this.enabledDisposables.clear();
|
||||
this.tree.refilter();
|
||||
this.render();
|
||||
this._enabled = false;
|
||||
this.triggered = false;
|
||||
this.widget = undefined;
|
||||
|
||||
this.enabledDisposables.dispose();
|
||||
this.enabledDisposables = new DisposableStore();
|
||||
|
||||
this.onDidChangeValue('');
|
||||
this.tree.domFocus();
|
||||
|
||||
this._onDidChangeOpenState.fire(false);
|
||||
}
|
||||
|
||||
private onEventOrInput(e: MouseEvent | StandardKeyboardEvent | string): void {
|
||||
if (typeof e === 'string') {
|
||||
this.onInput(e);
|
||||
} else if (e instanceof MouseEvent || e.keyCode === KeyCode.Escape || (e.keyCode === KeyCode.Backspace && (isMacintosh ? e.altKey : e.ctrlKey))) {
|
||||
this.onInput('');
|
||||
} else if (e.keyCode === KeyCode.Backspace) {
|
||||
this.onInput(this.pattern.length === 0 ? '' : this.pattern.substr(0, this.pattern.length - 1));
|
||||
} else {
|
||||
this.onInput(this.pattern + e.browserEvent.key);
|
||||
}
|
||||
}
|
||||
|
||||
private onInput(pattern: string): void {
|
||||
const container = this.view.getHTMLElement();
|
||||
|
||||
if (pattern && !this.domNode.parentElement) {
|
||||
container.append(this.domNode);
|
||||
} else if (!pattern && this.domNode.parentElement) {
|
||||
this.domNode.remove();
|
||||
this.tree.domFocus();
|
||||
}
|
||||
|
||||
private onDidChangeValue(pattern: string): void {
|
||||
this._pattern = pattern;
|
||||
this._onDidChangePattern.fire(pattern);
|
||||
|
||||
|
@ -836,75 +927,10 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
|
|||
}
|
||||
|
||||
this.render();
|
||||
|
||||
if (!pattern) {
|
||||
this.triggered = false;
|
||||
}
|
||||
}
|
||||
|
||||
private onDragStart(): void {
|
||||
const container = this.view.getHTMLElement();
|
||||
const { left } = getDomNodePagePosition(container);
|
||||
const containerWidth = container.clientWidth;
|
||||
const midContainerWidth = containerWidth / 2;
|
||||
const width = this.domNode.clientWidth;
|
||||
const disposables = new DisposableStore();
|
||||
let positionClassName = this.positionClassName;
|
||||
|
||||
const updatePosition = () => {
|
||||
switch (positionClassName) {
|
||||
case 'nw':
|
||||
this.domNode.style.top = `4px`;
|
||||
this.domNode.style.left = `4px`;
|
||||
break;
|
||||
case 'ne':
|
||||
this.domNode.style.top = `4px`;
|
||||
this.domNode.style.left = `${containerWidth - width - 6}px`;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const onDragOver = (event: DragEvent) => {
|
||||
event.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome)
|
||||
|
||||
const x = event.clientX - left;
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.dropEffect = 'none';
|
||||
}
|
||||
|
||||
if (x < midContainerWidth) {
|
||||
positionClassName = 'nw';
|
||||
} else {
|
||||
positionClassName = 'ne';
|
||||
}
|
||||
|
||||
updatePosition();
|
||||
};
|
||||
|
||||
const onDragEnd = () => {
|
||||
this.positionClassName = positionClassName;
|
||||
this.domNode.className = `monaco-list-type-filter ${this.positionClassName}`;
|
||||
this.domNode.style.top = '';
|
||||
this.domNode.style.left = '';
|
||||
|
||||
dispose(disposables);
|
||||
};
|
||||
|
||||
updatePosition();
|
||||
this.domNode.classList.remove(positionClassName);
|
||||
|
||||
this.domNode.classList.add('dragging');
|
||||
disposables.add(toDisposable(() => this.domNode.classList.remove('dragging')));
|
||||
|
||||
disposables.add(addDisposableListener(document, 'dragover', e => onDragOver(e)));
|
||||
disposables.add(addDisposableListener(this.domNode, 'dragend', () => onDragEnd()));
|
||||
|
||||
StaticDND.CurrentDragAndDropData = new DragAndDropData('vscode-ui');
|
||||
disposables.add(toDisposable(() => StaticDND.CurrentDragAndDropData = undefined));
|
||||
}
|
||||
|
||||
private onDidSpliceModel(): void {
|
||||
if (!this._enabled || this.pattern.length === 0) {
|
||||
if (!this.widget || this.pattern.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -912,46 +938,18 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
|
|||
this.render();
|
||||
}
|
||||
|
||||
private onDidChangeFilterOnType(): void {
|
||||
this.tree.updateOptions({ filterOnType: this.filterOnTypeDomNode.checked });
|
||||
this.tree.refilter();
|
||||
this.tree.domFocus();
|
||||
this.render();
|
||||
this.updateFilterOnTypeTitleAndIcon();
|
||||
}
|
||||
|
||||
private updateFilterOnTypeTitleAndIcon(): void {
|
||||
if (this.filterOnType) {
|
||||
this.filterOnTypeDomNode.classList.remove(...Codicon.treeFilterOnTypeOff.classNamesArray);
|
||||
this.filterOnTypeDomNode.classList.add(...Codicon.treeFilterOnTypeOn.classNamesArray);
|
||||
this.filterOnTypeDomNode.title = localize('disable filter on type', "Disable Filter on Type");
|
||||
} else {
|
||||
this.filterOnTypeDomNode.classList.remove(...Codicon.treeFilterOnTypeOn.classNamesArray);
|
||||
this.filterOnTypeDomNode.classList.add(...Codicon.treeFilterOnTypeOff.classNamesArray);
|
||||
this.filterOnTypeDomNode.title = localize('enable filter on type', "Enable Filter on Type");
|
||||
}
|
||||
}
|
||||
|
||||
private render(): void {
|
||||
const noMatches = this.filter.totalCount > 0 && this.filter.matchCount === 0;
|
||||
|
||||
if (this.pattern && this.tree.options.filterOnType && noMatches) {
|
||||
this.messageDomNode.textContent = localize('empty', "No elements found");
|
||||
this._empty = true;
|
||||
if (this.pattern && noMatches) {
|
||||
this.widget?.showMessage({ type: MessageType.WARNING, content: localize('not found', "No elements found.") });
|
||||
} else {
|
||||
this.messageDomNode.innerText = '';
|
||||
this._empty = false;
|
||||
this.widget?.clearMessage();
|
||||
}
|
||||
|
||||
this.domNode.classList.toggle('no-matches', noMatches);
|
||||
this.domNode.title = localize('found', "Matched {0} out of {1} elements", this.filter.matchCount, this.filter.totalCount);
|
||||
this.labelDomNode.textContent = this.pattern.length > 16 ? '…' + this.pattern.substr(this.pattern.length - 16) : this.pattern;
|
||||
|
||||
this._onDidChangeEmptyState.fire(this._empty);
|
||||
}
|
||||
|
||||
shouldAllowFocus(node: ITreeNode<T, TFilterData>): boolean {
|
||||
if (!this.enabled || !this.pattern || this.filterOnType) {
|
||||
if (!this.widget || !this.pattern || this._mode === TreeFindMode.Filter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -962,16 +960,20 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
|
|||
return !FuzzyScore.isDefault(node.filterData as any as FuzzyScore);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this._enabled) {
|
||||
this.domNode.remove();
|
||||
this.enabledDisposables.dispose();
|
||||
this._enabled = false;
|
||||
this.triggered = false;
|
||||
}
|
||||
style(styles: IFindWidgetStyles): void {
|
||||
this.styles = styles;
|
||||
this.widget?.style(styles);
|
||||
}
|
||||
|
||||
layout(width: number): void {
|
||||
this.width = width;
|
||||
this.widget?.layout(width);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._onDidChangePattern.dispose();
|
||||
dispose(this.disposables);
|
||||
this.enabledDisposables.dispose();
|
||||
this.disposables.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -982,6 +984,8 @@ function asTreeMouseEvent<T>(event: IListMouseEvent<ITreeNode<T, any>>): ITreeMo
|
|||
target = TreeMouseEventTarget.Twistie;
|
||||
} else if (hasParentWithClass(event.browserEvent.target as HTMLElement, 'monaco-tl-contents', 'monaco-tl-row')) {
|
||||
target = TreeMouseEventTarget.Element;
|
||||
} else if (hasParentWithClass(event.browserEvent.target as HTMLElement, 'monaco-tree-type-filter', 'monaco-list')) {
|
||||
target = TreeMouseEventTarget.Filter;
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -1005,9 +1009,9 @@ export interface IKeyboardNavigationEventFilter {
|
|||
|
||||
export interface IAbstractTreeOptionsUpdate extends ITreeRendererOptions {
|
||||
readonly multipleSelectionSupport?: boolean;
|
||||
readonly automaticKeyboardNavigation?: boolean;
|
||||
readonly simpleKeyboardNavigation?: boolean;
|
||||
readonly filterOnType?: boolean;
|
||||
readonly typeNavigationEnabled?: boolean;
|
||||
readonly typeNavigationMode?: TypeNavigationMode;
|
||||
readonly defaultFindMode?: TreeFindMode;
|
||||
readonly smoothScrolling?: boolean;
|
||||
readonly horizontalScrolling?: boolean;
|
||||
readonly mouseWheelScrollSensitivity?: number;
|
||||
|
@ -1017,6 +1021,7 @@ export interface IAbstractTreeOptionsUpdate extends ITreeRendererOptions {
|
|||
}
|
||||
|
||||
export interface IAbstractTreeOptions<T, TFilterData = void> extends IAbstractTreeOptionsUpdate, IListOptions<T> {
|
||||
readonly contextViewProvider?: IContextViewProvider;
|
||||
readonly collapseByDefault?: boolean; // defaults to false
|
||||
readonly filter?: ITreeFilter<T, TFilterData>;
|
||||
readonly dnd?: ITreeDragAndDrop<T>;
|
||||
|
@ -1318,7 +1323,8 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
|||
private selection: Trait<T>;
|
||||
private anchor: Trait<T>;
|
||||
private eventBufferer = new EventBufferer();
|
||||
private typeFilterController?: TypeFilterController<T, TFilterData>;
|
||||
private findController?: FindController<T, TFilterData>;
|
||||
readonly onDidChangeFindOpenState: Event<boolean> = Event.None;
|
||||
private focusNavigationFilter: ((node: ITreeNode<T, TFilterData>) => boolean) | undefined;
|
||||
private styleElement: HTMLStyleElement;
|
||||
protected readonly disposables = new DisposableStore();
|
||||
|
@ -1329,7 +1335,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
|||
get onDidChangeSelection(): Event<ITreeEvent<T>> { return this.eventBufferer.wrapEvent(this.selection.onDidChange); }
|
||||
|
||||
get onMouseClick(): Event<ITreeMouseEvent<T>> { return Event.map(this.view.onMouseClick, asTreeMouseEvent); }
|
||||
get onMouseDblClick(): Event<ITreeMouseEvent<T>> { return Event.map(this.view.onMouseDblClick, asTreeMouseEvent); }
|
||||
get onMouseDblClick(): Event<ITreeMouseEvent<T>> { return Event.filter(Event.map(this.view.onMouseDblClick, asTreeMouseEvent), e => e.target !== TreeMouseEventTarget.Filter); }
|
||||
get onContextMenu(): Event<ITreeContextMenuEvent<T>> { return Event.map(this.view.onContextMenu, asTreeContextMenuEvent); }
|
||||
get onTap(): Event<ITreeMouseEvent<T>> { return Event.map(this.view.onTap, asTreeMouseEvent); }
|
||||
get onPointer(): Event<ITreeMouseEvent<T>> { return Event.map(this.view.onPointer, asTreeMouseEvent); }
|
||||
|
@ -1348,8 +1354,11 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
|||
private readonly _onWillRefilter = new Emitter<void>();
|
||||
readonly onWillRefilter: Event<void> = this._onWillRefilter.event;
|
||||
|
||||
get filterOnType(): boolean { return !!this._options.filterOnType; }
|
||||
get onDidChangeTypeFilterPattern(): Event<string> { return this.typeFilterController ? this.typeFilterController.onDidChangePattern : Event.None; }
|
||||
get findMode(): TreeFindMode { return this.findController?.mode ?? TreeFindMode.Highlight; }
|
||||
set findMode(findMode: TreeFindMode) { if (this.findController) { this.findController.mode = findMode; } }
|
||||
readonly onDidChangeFindMode: Event<TreeFindMode>;
|
||||
|
||||
get onDidChangeFindPattern(): Event<string> { return this.findController ? this.findController.onDidChangePattern : Event.None; }
|
||||
|
||||
get expandOnDoubleClick(): boolean { return typeof this._options.expandOnDoubleClick === 'undefined' ? true : this._options.expandOnDoubleClick; }
|
||||
get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) { return typeof this._options.expandOnlyOnTwistieClick === 'undefined' ? true : this._options.expandOnlyOnTwistieClick; }
|
||||
|
@ -1376,10 +1385,10 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
|||
this.disposables.add(r);
|
||||
}
|
||||
|
||||
let filter: TypeFilter<T> | undefined;
|
||||
let filter: FindFilter<T> | undefined;
|
||||
|
||||
if (_options.keyboardNavigationLabelProvider) {
|
||||
filter = new TypeFilter(this, _options.keyboardNavigationLabelProvider, _options.filter as any as ITreeFilter<T, FuzzyScore>);
|
||||
filter = new FindFilter(this, _options.keyboardNavigationLabelProvider, _options.filter as any as ITreeFilter<T, FuzzyScore>);
|
||||
_options = { ..._options, filter: filter as ITreeFilter<T, TFilterData> }; // TODO need typescript help here
|
||||
this.disposables.add(filter);
|
||||
}
|
||||
|
@ -1432,11 +1441,14 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
|||
onKeyDown.filter(e => e.keyCode === KeyCode.Space).on(this.onSpace, this, this.disposables);
|
||||
}
|
||||
|
||||
if (_options.keyboardNavigationLabelProvider) {
|
||||
const delegate = _options.keyboardNavigationDelegate || DefaultKeyboardNavigationDelegate;
|
||||
this.typeFilterController = new TypeFilterController(this, this.model, this.view, filter!, delegate);
|
||||
this.focusNavigationFilter = node => this.typeFilterController!.shouldAllowFocus(node);
|
||||
this.disposables.add(this.typeFilterController!);
|
||||
if (_options.keyboardNavigationLabelProvider && _options.contextViewProvider) {
|
||||
this.findController = new FindController(this, this.model, this.view, filter!, _options.contextViewProvider);
|
||||
this.focusNavigationFilter = node => this.findController!.shouldAllowFocus(node);
|
||||
this.onDidChangeFindOpenState = this.findController.onDidChangeOpenState;
|
||||
this.disposables.add(this.findController!);
|
||||
this.onDidChangeFindMode = this.findController.onDidChangeMode;
|
||||
} else {
|
||||
this.onDidChangeFindMode = Event.None;
|
||||
}
|
||||
|
||||
this.styleElement = createStyleSheet(this.view.getHTMLElement());
|
||||
|
@ -1450,13 +1462,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
|||
renderer.updateOptions(optionsUpdate);
|
||||
}
|
||||
|
||||
this.view.updateOptions({
|
||||
...this._options,
|
||||
enableKeyboardNavigation: this._options.simpleKeyboardNavigation,
|
||||
});
|
||||
|
||||
this.typeFilterController?.updateOptions(this._options);
|
||||
|
||||
this.view.updateOptions(this._options);
|
||||
this._onDidUpdateOptions.fire(this._options);
|
||||
|
||||
this.getHTMLElement().classList.toggle('always', this._options.renderIndentGuides === RenderIndentGuides.Always);
|
||||
|
@ -1483,21 +1489,11 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
|||
}
|
||||
|
||||
get contentHeight(): number {
|
||||
if (this.typeFilterController && this.typeFilterController.filterOnType && this.typeFilterController.empty) {
|
||||
return 100;
|
||||
}
|
||||
|
||||
return this.view.contentHeight;
|
||||
}
|
||||
|
||||
get onDidChangeContentHeight(): Event<number> {
|
||||
let result = this.view.onDidChangeContentHeight;
|
||||
|
||||
if (this.typeFilterController) {
|
||||
result = Event.any(result, Event.map(this.typeFilterController.onDidChangeEmptyState, () => this.contentHeight));
|
||||
}
|
||||
|
||||
return result;
|
||||
return this.view.onDidChangeContentHeight;
|
||||
}
|
||||
|
||||
get scrollTop(): number {
|
||||
|
@ -1559,6 +1555,10 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
|||
|
||||
layout(height?: number, width?: number): void {
|
||||
this.view.layout(height, width);
|
||||
|
||||
if (isNumber(width)) {
|
||||
this.findController?.layout(width);
|
||||
}
|
||||
}
|
||||
|
||||
style(styles: IListStyles): void {
|
||||
|
@ -1571,6 +1571,8 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
|||
}
|
||||
|
||||
this.styleElement.textContent = content.join('\n');
|
||||
|
||||
this.findController?.style(styles);
|
||||
this.view.style(styles);
|
||||
}
|
||||
|
||||
|
@ -1624,12 +1626,16 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
|||
return this.model.isCollapsed(location);
|
||||
}
|
||||
|
||||
toggleKeyboardNavigation(): void {
|
||||
this.view.toggleKeyboardNavigation();
|
||||
triggerTypeNavigation(): void {
|
||||
this.view.triggerTypeNavigation();
|
||||
}
|
||||
|
||||
if (this.typeFilterController) {
|
||||
this.typeFilterController.toggle();
|
||||
}
|
||||
openFind(): void {
|
||||
this.findController?.open();
|
||||
}
|
||||
|
||||
closeFind(): void {
|
||||
this.findController?.close();
|
||||
}
|
||||
|
||||
refilter(): void {
|
||||
|
|
|
@ -7,7 +7,7 @@ import { IDragAndDropData } from 'vs/base/browser/dnd';
|
|||
import { IIdentityProvider, IListDragAndDrop, IListDragOverReaction, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
|
||||
import { IListStyles } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { ComposedTreeDelegate, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ComposedTreeDelegate, TreeFindMode as TreeFindMode, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ICompressedTreeElement, ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
|
||||
import { getVisibleState, isFilterResult } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { CompressibleObjectTree, ICompressibleKeyboardNavigationLabelProvider, ICompressibleObjectTreeOptions, ICompressibleTreeRenderer, IObjectTreeOptions, IObjectTreeSetChildrenOptions, ObjectTree } from 'vs/base/browser/ui/tree/objectTree';
|
||||
|
@ -341,7 +341,12 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
|||
|
||||
get onDidUpdateOptions(): Event<IAsyncDataTreeOptionsUpdate> { return this.tree.onDidUpdateOptions; }
|
||||
|
||||
get filterOnType(): boolean { return this.tree.filterOnType; }
|
||||
get onDidChangeFindOpenState(): Event<boolean> { return this.tree.onDidChangeFindOpenState; }
|
||||
|
||||
get findMode(): TreeFindMode { return this.tree.findMode; }
|
||||
set findMode(mode: TreeFindMode) { this.tree.findMode = mode; }
|
||||
readonly onDidChangeFindMode: Event<TreeFindMode>;
|
||||
|
||||
get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) {
|
||||
if (typeof this.tree.expandOnlyOnTwistieClick === 'boolean') {
|
||||
return this.tree.expandOnlyOnTwistieClick;
|
||||
|
@ -367,6 +372,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
|||
this.collapseByDefault = options.collapseByDefault;
|
||||
|
||||
this.tree = this.createTree(user, container, delegate, renderers, options);
|
||||
this.onDidChangeFindMode = this.tree.onDidChangeFindMode;
|
||||
|
||||
this.root = createAsyncDataTreeNode({
|
||||
element: undefined!,
|
||||
|
@ -616,8 +622,16 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
|||
return this.tree.isCollapsed(this.getDataNode(element));
|
||||
}
|
||||
|
||||
toggleKeyboardNavigation(): void {
|
||||
this.tree.toggleKeyboardNavigation();
|
||||
triggerTypeNavigation(): void {
|
||||
this.tree.triggerTypeNavigation();
|
||||
}
|
||||
|
||||
openFind(): void {
|
||||
this.tree.openFind();
|
||||
}
|
||||
|
||||
closeFind(): void {
|
||||
this.tree.closeFind();
|
||||
}
|
||||
|
||||
refilter(): void {
|
||||
|
|
|
@ -67,3 +67,45 @@
|
|||
/* Use steps to throttle FPS to reduce CPU usage */
|
||||
animation: codicon-spin 1.25s steps(30) infinite;
|
||||
}
|
||||
|
||||
.monaco-tree-type-filter {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
display: flex;
|
||||
padding: 3px;
|
||||
transition: top 0.3s;
|
||||
width: 160px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.monaco-tree-type-filter.disabled {
|
||||
top: -40px;
|
||||
}
|
||||
|
||||
.monaco-tree-type-filter-grab {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.monaco-tree-type-filter-grab.grabbing {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.monaco-tree-type-filter-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.monaco-tree-type-filter-input .monaco-inputbox > .ibwrapper > .input,
|
||||
.monaco-tree-type-filter-input .monaco-inputbox > .ibwrapper > .mirror {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.monaco-tree-type-filter-actionbar {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.monaco-tree-type-filter-actionbar .monaco-action-bar .action-label {
|
||||
padding: 2px;
|
||||
}
|
||||
|
|
|
@ -141,7 +141,8 @@ export interface ITreeEvent<T> {
|
|||
export enum TreeMouseEventTarget {
|
||||
Unknown,
|
||||
Twistie,
|
||||
Element
|
||||
Element,
|
||||
Filter
|
||||
}
|
||||
|
||||
export interface ITreeMouseEvent<T> {
|
||||
|
|
|
@ -15,8 +15,8 @@ export interface ITelemetryData {
|
|||
|
||||
export type WorkbenchActionExecutedClassification = {
|
||||
owner: 'bpasero';
|
||||
id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
|
||||
from: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
|
||||
id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the action that was run.' };
|
||||
from: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The name of the component the action was run from.' };
|
||||
};
|
||||
|
||||
export type WorkbenchActionExecutedEvent = {
|
||||
|
|
|
@ -390,6 +390,9 @@ interface IEnsuredWriteFileOptions extends IWriteFileOptions {
|
|||
}
|
||||
|
||||
let canFlush = true;
|
||||
export function configureFlushOnWrite(enabled: boolean): void {
|
||||
canFlush = enabled;
|
||||
}
|
||||
|
||||
// Calls fs.writeFile() followed by a fs.sync() call to flush the changes to disk
|
||||
// We do this in cases where we want to make sure the data is really on disk and
|
||||
|
@ -421,7 +424,7 @@ function doWriteFileAndFlush(path: string, data: string | Buffer | Uint8Array, o
|
|||
// In that case we disable flushing and warn to the console
|
||||
if (syncError) {
|
||||
console.warn('[node.js fs] fdatasync is now disabled for this session because it failed: ', syncError);
|
||||
canFlush = false;
|
||||
configureFlushOnWrite(false);
|
||||
}
|
||||
|
||||
return fs.close(fd, closeError => callback(closeError));
|
||||
|
@ -455,7 +458,7 @@ export function writeFileSync(path: string, data: string | Buffer, options?: IWr
|
|||
fs.fdatasyncSync(fd); // https://github.com/microsoft/vscode/issues/9589
|
||||
} catch (syncError) {
|
||||
console.warn('[node.js fs] fdatasyncSync is now disabled for this session because it failed: ', syncError);
|
||||
canFlush = false;
|
||||
configureFlushOnWrite(false);
|
||||
}
|
||||
} finally {
|
||||
fs.closeSync(fd);
|
||||
|
|
|
@ -52,7 +52,7 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
|
|||
const ISSUE_REPORTER_HINT = /--vscode-window-kind=issue-reporter/;
|
||||
const PROCESS_EXPLORER_HINT = /--vscode-window-kind=process-explorer/;
|
||||
const UTILITY_NETWORK_HINT = /--utility-sub-type=network/;
|
||||
const UTILITY_EXTENSION_HOST_HINT = /--vscode-utility-kind=extension-host/;
|
||||
const UTILITY_EXTENSION_HOST_HINT = /--utility-sub-type=node.mojom.NodeService/;
|
||||
const WINDOWS_CRASH_REPORTER = /--crashes-directory/;
|
||||
const WINDOWS_PTY = /\\pipe\\winpty-control/;
|
||||
const WINDOWS_CONSOLE_HOST = /conhost\.exe/;
|
||||
|
|
|
@ -670,56 +670,57 @@ flakySuite('SQLite Storage Library', function () {
|
|||
});
|
||||
|
||||
test('multiple concurrent writes execute in sequence', async () => {
|
||||
|
||||
class TestStorage extends Storage {
|
||||
getStorage(): IStorageDatabase {
|
||||
return this.database;
|
||||
return runWithFakedTimers({}, async () => {
|
||||
class TestStorage extends Storage {
|
||||
getStorage(): IStorageDatabase {
|
||||
return this.database;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const storage = new TestStorage(new SQLiteStorageDatabase(join(testdir, 'storage.db')));
|
||||
const storage = new TestStorage(new SQLiteStorageDatabase(join(testdir, 'storage.db')));
|
||||
|
||||
await storage.init();
|
||||
await storage.init();
|
||||
|
||||
storage.set('foo', 'bar');
|
||||
storage.set('some/foo/path', 'some/bar/path');
|
||||
storage.set('foo', 'bar');
|
||||
storage.set('some/foo/path', 'some/bar/path');
|
||||
|
||||
await timeout(2);
|
||||
await timeout(2);
|
||||
|
||||
storage.set('foo1', 'bar');
|
||||
storage.set('some/foo1/path', 'some/bar/path');
|
||||
storage.set('foo1', 'bar');
|
||||
storage.set('some/foo1/path', 'some/bar/path');
|
||||
|
||||
await timeout(2);
|
||||
await timeout(2);
|
||||
|
||||
storage.set('foo2', 'bar');
|
||||
storage.set('some/foo2/path', 'some/bar/path');
|
||||
storage.set('foo2', 'bar');
|
||||
storage.set('some/foo2/path', 'some/bar/path');
|
||||
|
||||
await timeout(2);
|
||||
await timeout(2);
|
||||
|
||||
storage.delete('foo1');
|
||||
storage.delete('some/foo1/path');
|
||||
storage.delete('foo1');
|
||||
storage.delete('some/foo1/path');
|
||||
|
||||
await timeout(2);
|
||||
await timeout(2);
|
||||
|
||||
storage.delete('foo4');
|
||||
storage.delete('some/foo4/path');
|
||||
storage.delete('foo4');
|
||||
storage.delete('some/foo4/path');
|
||||
|
||||
await timeout(5);
|
||||
await timeout(5);
|
||||
|
||||
storage.set('foo3', 'bar');
|
||||
await storage.set('some/foo3/path', 'some/bar/path');
|
||||
storage.set('foo3', 'bar');
|
||||
await storage.set('some/foo3/path', 'some/bar/path');
|
||||
|
||||
const items = await storage.getStorage().getItems();
|
||||
strictEqual(items.get('foo'), 'bar');
|
||||
strictEqual(items.get('some/foo/path'), 'some/bar/path');
|
||||
strictEqual(items.has('foo1'), false);
|
||||
strictEqual(items.has('some/foo1/path'), false);
|
||||
strictEqual(items.get('foo2'), 'bar');
|
||||
strictEqual(items.get('some/foo2/path'), 'some/bar/path');
|
||||
strictEqual(items.get('foo3'), 'bar');
|
||||
strictEqual(items.get('some/foo3/path'), 'some/bar/path');
|
||||
const items = await storage.getStorage().getItems();
|
||||
strictEqual(items.get('foo'), 'bar');
|
||||
strictEqual(items.get('some/foo/path'), 'some/bar/path');
|
||||
strictEqual(items.has('foo1'), false);
|
||||
strictEqual(items.has('some/foo1/path'), false);
|
||||
strictEqual(items.get('foo2'), 'bar');
|
||||
strictEqual(items.get('some/foo2/path'), 'some/bar/path');
|
||||
strictEqual(items.get('foo3'), 'bar');
|
||||
strictEqual(items.get('some/foo3/path'), 'some/bar/path');
|
||||
|
||||
await storage.close();
|
||||
await storage.close();
|
||||
});
|
||||
});
|
||||
|
||||
test('lots of INSERT & DELETE (below inline max)', async () => {
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
const $ = dom.$;
|
||||
import { $, h, multibyteAwareBtoa } from 'vs/base/browser/dom';
|
||||
|
||||
suite('dom', () => {
|
||||
test('hasClass', () => {
|
||||
|
@ -73,9 +72,9 @@ suite('dom', () => {
|
|||
});
|
||||
|
||||
test('multibyteAwareBtoa', () => {
|
||||
assert.ok(dom.multibyteAwareBtoa('hello world').length > 0);
|
||||
assert.ok(dom.multibyteAwareBtoa('平仮名').length > 0);
|
||||
assert.ok(dom.multibyteAwareBtoa(new Array(100000).fill('vs').join('')).length > 0); // https://github.com/microsoft/vscode/issues/112013
|
||||
assert.ok(multibyteAwareBtoa('hello world').length > 0);
|
||||
assert.ok(multibyteAwareBtoa('平仮名').length > 0);
|
||||
assert.ok(multibyteAwareBtoa(new Array(100000).fill('vs').join('')).length > 0); // https://github.com/microsoft/vscode/issues/112013
|
||||
});
|
||||
|
||||
suite('$', () => {
|
||||
|
@ -129,4 +128,152 @@ suite('dom', () => {
|
|||
assert.strictEqual(firstChild.textContent, 'foobar');
|
||||
});
|
||||
});
|
||||
|
||||
suite('h', () => {
|
||||
test('should build simple nodes', () => {
|
||||
const div = h('div');
|
||||
assert(div.root instanceof HTMLElement);
|
||||
assert.strictEqual(div.root.tagName, 'DIV');
|
||||
|
||||
const span = h('span');
|
||||
assert(span.root instanceof HTMLElement);
|
||||
assert.strictEqual(span.root.tagName, 'SPAN');
|
||||
|
||||
const img = h('img');
|
||||
assert(img.root instanceof HTMLElement);
|
||||
assert.strictEqual(img.root.tagName, 'IMG');
|
||||
});
|
||||
|
||||
test('should handle ids and classes', () => {
|
||||
const divId = h('div#myid');
|
||||
assert.strictEqual(divId.root.tagName, 'DIV');
|
||||
assert.strictEqual(divId.root.id, 'myid');
|
||||
|
||||
const divClass = h('div.a');
|
||||
assert.strictEqual(divClass.root.tagName, 'DIV');
|
||||
assert.strictEqual(divClass.root.classList.length, 1);
|
||||
assert(divClass.root.classList.contains('a'));
|
||||
|
||||
const divClasses = h('div.a.b.c');
|
||||
assert.strictEqual(divClasses.root.tagName, 'DIV');
|
||||
assert.strictEqual(divClasses.root.classList.length, 3);
|
||||
assert(divClasses.root.classList.contains('a'));
|
||||
assert(divClasses.root.classList.contains('b'));
|
||||
assert(divClasses.root.classList.contains('c'));
|
||||
|
||||
const divAll = h('div#myid.a.b.c');
|
||||
assert.strictEqual(divAll.root.tagName, 'DIV');
|
||||
assert.strictEqual(divAll.root.id, 'myid');
|
||||
assert.strictEqual(divAll.root.classList.length, 3);
|
||||
assert(divAll.root.classList.contains('a'));
|
||||
assert(divAll.root.classList.contains('b'));
|
||||
assert(divAll.root.classList.contains('c'));
|
||||
|
||||
const spanId = h('span#myid');
|
||||
assert.strictEqual(spanId.root.tagName, 'SPAN');
|
||||
assert.strictEqual(spanId.root.id, 'myid');
|
||||
|
||||
const spanClass = h('span.a');
|
||||
assert.strictEqual(spanClass.root.tagName, 'SPAN');
|
||||
assert.strictEqual(spanClass.root.classList.length, 1);
|
||||
assert(spanClass.root.classList.contains('a'));
|
||||
|
||||
const spanClasses = h('span.a.b.c');
|
||||
assert.strictEqual(spanClasses.root.tagName, 'SPAN');
|
||||
assert.strictEqual(spanClasses.root.classList.length, 3);
|
||||
assert(spanClasses.root.classList.contains('a'));
|
||||
assert(spanClasses.root.classList.contains('b'));
|
||||
assert(spanClasses.root.classList.contains('c'));
|
||||
|
||||
const spanAll = h('span#myid.a.b.c');
|
||||
assert.strictEqual(spanAll.root.tagName, 'SPAN');
|
||||
assert.strictEqual(spanAll.root.id, 'myid');
|
||||
assert.strictEqual(spanAll.root.classList.length, 3);
|
||||
assert(spanAll.root.classList.contains('a'));
|
||||
assert(spanAll.root.classList.contains('b'));
|
||||
assert(spanAll.root.classList.contains('c'));
|
||||
});
|
||||
|
||||
test('should implicitly handle ids and classes', () => {
|
||||
const divId = h('#myid');
|
||||
assert.strictEqual(divId.root.tagName, 'DIV');
|
||||
assert.strictEqual(divId.root.id, 'myid');
|
||||
|
||||
const divClass = h('.a');
|
||||
assert.strictEqual(divClass.root.tagName, 'DIV');
|
||||
assert.strictEqual(divClass.root.classList.length, 1);
|
||||
assert(divClass.root.classList.contains('a'));
|
||||
|
||||
const divClasses = h('.a.b.c');
|
||||
assert.strictEqual(divClasses.root.tagName, 'DIV');
|
||||
assert.strictEqual(divClasses.root.classList.length, 3);
|
||||
assert(divClasses.root.classList.contains('a'));
|
||||
assert(divClasses.root.classList.contains('b'));
|
||||
assert(divClasses.root.classList.contains('c'));
|
||||
|
||||
const divAll = h('#myid.a.b.c');
|
||||
assert.strictEqual(divAll.root.tagName, 'DIV');
|
||||
assert.strictEqual(divAll.root.id, 'myid');
|
||||
assert.strictEqual(divAll.root.classList.length, 3);
|
||||
assert(divAll.root.classList.contains('a'));
|
||||
assert(divAll.root.classList.contains('b'));
|
||||
assert(divAll.root.classList.contains('c'));
|
||||
});
|
||||
|
||||
test('should handle @ identifiers', () => {
|
||||
const implicit = h('@el');
|
||||
assert.strictEqual(implicit.root, implicit.el);
|
||||
assert.strictEqual(implicit.el.tagName, 'DIV');
|
||||
|
||||
const explicit = h('div@el');
|
||||
assert.strictEqual(explicit.root, explicit.el);
|
||||
assert.strictEqual(explicit.el.tagName, 'DIV');
|
||||
|
||||
const implicitId = h('#myid@el');
|
||||
assert.strictEqual(implicitId.root, implicitId.el);
|
||||
assert.strictEqual(implicitId.el.tagName, 'DIV');
|
||||
assert.strictEqual(implicitId.root.id, 'myid');
|
||||
|
||||
const explicitId = h('div#myid@el');
|
||||
assert.strictEqual(explicitId.root, explicitId.el);
|
||||
assert.strictEqual(explicitId.el.tagName, 'DIV');
|
||||
assert.strictEqual(explicitId.root.id, 'myid');
|
||||
|
||||
const implicitClass = h('.a@el');
|
||||
assert.strictEqual(implicitClass.root, implicitClass.el);
|
||||
assert.strictEqual(implicitClass.el.tagName, 'DIV');
|
||||
assert.strictEqual(implicitClass.root.classList.length, 1);
|
||||
assert(implicitClass.root.classList.contains('a'));
|
||||
|
||||
const explicitClass = h('div.a@el');
|
||||
assert.strictEqual(explicitClass.root, explicitClass.el);
|
||||
assert.strictEqual(explicitClass.el.tagName, 'DIV');
|
||||
assert.strictEqual(explicitClass.root.classList.length, 1);
|
||||
assert(explicitClass.root.classList.contains('a'));
|
||||
});
|
||||
});
|
||||
|
||||
test('should recurse', () => {
|
||||
const result = h('div.code-view', [
|
||||
h('div.title@title'),
|
||||
h('div.container', [
|
||||
h('div.gutter@gutterDiv'),
|
||||
h('span@editor'),
|
||||
]),
|
||||
]);
|
||||
|
||||
assert.strictEqual(result.root.tagName, 'DIV');
|
||||
assert.strictEqual(result.root.className, 'code-view');
|
||||
assert.strictEqual(result.root.childElementCount, 2);
|
||||
assert.strictEqual(result.root.firstElementChild, result.title);
|
||||
assert.strictEqual(result.title.tagName, 'DIV');
|
||||
assert.strictEqual(result.title.className, 'title');
|
||||
assert.strictEqual(result.title.childElementCount, 0);
|
||||
assert.strictEqual(result.gutterDiv.tagName, 'DIV');
|
||||
assert.strictEqual(result.gutterDiv.className, 'gutter');
|
||||
assert.strictEqual(result.gutterDiv.childElementCount, 0);
|
||||
assert.strictEqual(result.editor.tagName, 'SPAN');
|
||||
assert.strictEqual(result.editor.className, '');
|
||||
assert.strictEqual(result.editor.childElementCount, 0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,9 +11,11 @@ import { VSBuffer } from 'vs/base/common/buffer';
|
|||
import { randomPath } from 'vs/base/common/extpath';
|
||||
import { join, sep } from 'vs/base/common/path';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { Promises, RimRafMode, rimrafSync, SymlinkSupport, writeFileSync } from 'vs/base/node/pfs';
|
||||
import { configureFlushOnWrite, Promises, RimRafMode, rimrafSync, SymlinkSupport, writeFileSync } from 'vs/base/node/pfs';
|
||||
import { flakySuite, getPathFromAmdModule, getRandomTestPath } from 'vs/base/test/node/testUtils';
|
||||
|
||||
configureFlushOnWrite(false); // speed up all unit tests by disabling flush on write
|
||||
|
||||
flakySuite('PFS', function () {
|
||||
|
||||
let testDir: string;
|
||||
|
@ -368,24 +370,36 @@ flakySuite('PFS', function () {
|
|||
const smallData = 'Hello World';
|
||||
const bigData = (new Array(100 * 1024)).join('Large String\n');
|
||||
|
||||
return testWriteFileAndFlush(smallData, smallData, bigData, bigData);
|
||||
return testWriteFile(smallData, smallData, bigData, bigData);
|
||||
});
|
||||
|
||||
test('writeFile (string) - flush on write', async () => {
|
||||
configureFlushOnWrite(true);
|
||||
try {
|
||||
const smallData = 'Hello World';
|
||||
const bigData = (new Array(100 * 1024)).join('Large String\n');
|
||||
|
||||
return await testWriteFile(smallData, smallData, bigData, bigData);
|
||||
} finally {
|
||||
configureFlushOnWrite(false);
|
||||
}
|
||||
});
|
||||
|
||||
test('writeFile (Buffer)', async () => {
|
||||
const smallData = 'Hello World';
|
||||
const bigData = (new Array(100 * 1024)).join('Large String\n');
|
||||
|
||||
return testWriteFileAndFlush(Buffer.from(smallData), smallData, Buffer.from(bigData), bigData);
|
||||
return testWriteFile(Buffer.from(smallData), smallData, Buffer.from(bigData), bigData);
|
||||
});
|
||||
|
||||
test('writeFile (UInt8Array)', async () => {
|
||||
const smallData = 'Hello World';
|
||||
const bigData = (new Array(100 * 1024)).join('Large String\n');
|
||||
|
||||
return testWriteFileAndFlush(VSBuffer.fromString(smallData).buffer, smallData, VSBuffer.fromString(bigData).buffer, bigData);
|
||||
return testWriteFile(VSBuffer.fromString(smallData).buffer, smallData, VSBuffer.fromString(bigData).buffer, bigData);
|
||||
});
|
||||
|
||||
async function testWriteFileAndFlush(
|
||||
async function testWriteFile(
|
||||
smallData: string | Buffer | Uint8Array,
|
||||
smallDataValue: string,
|
||||
bigData: string | Buffer | Uint8Array,
|
||||
|
|
|
@ -53,7 +53,6 @@ import { FollowerLogService, LoggerChannelClient, LogLevelChannelClient } from '
|
|||
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { RequestService } from 'vs/platform/request/browser/requestService';
|
||||
import { IRequestService } from 'vs/platform/request/common/request';
|
||||
import { ISharedProcessConfiguration } from 'vs/platform/sharedProcess/node/sharedProcess';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
|
@ -106,6 +105,7 @@ import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/pol
|
|||
import { UserDataProfilesNativeService } from 'vs/platform/userDataProfile/electron-sandbox/userDataProfile';
|
||||
import { OneDataSystemWebAppender } from 'vs/platform/telemetry/browser/1dsAppender';
|
||||
import { DefaultExtensionsProfileInitService } from 'vs/platform/extensionManagement/electron-sandbox/defaultExtensionsProfileInit';
|
||||
import { SharedProcessRequestService } from 'vs/platform/request/electron-browser/sharedProcessRequestService';
|
||||
|
||||
class SharedProcessMain extends Disposable {
|
||||
|
||||
|
@ -254,7 +254,7 @@ class SharedProcessMain extends Disposable {
|
|||
services.set(IUriIdentityService, new UriIdentityService(fileService));
|
||||
|
||||
// Request
|
||||
services.set(IRequestService, new SyncDescriptor(RequestService));
|
||||
services.set(IRequestService, new SharedProcessRequestService(mainProcessService, configurationService, logService));
|
||||
|
||||
// Checksum
|
||||
services.set(IChecksumService, new SyncDescriptor(ChecksumService));
|
||||
|
|
|
@ -104,6 +104,8 @@ import { PolicyChannel } from 'vs/platform/policy/common/policyIpc';
|
|||
import { IUserDataProfilesMainService } from 'vs/platform/userDataProfile/electron-main/userDataProfile';
|
||||
import { IDefaultExtensionsProfileInitService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { DefaultExtensionsProfileInitHandler } from 'vs/platform/extensionManagement/electron-main/defaultExtensionsProfileInit';
|
||||
import { RequestChannel } from 'vs/platform/request/common/requestIpc';
|
||||
import { IRequestService } from 'vs/platform/request/common/request';
|
||||
|
||||
/**
|
||||
* The main VS Code application. There will only ever be one instance,
|
||||
|
@ -728,6 +730,10 @@ export class CodeApplication extends Disposable {
|
|||
mainProcessElectronServer.registerChannel('userDataProfiles', userDataProfilesService);
|
||||
sharedProcessClient.then(client => client.registerChannel('userDataProfiles', userDataProfilesService));
|
||||
|
||||
// Request
|
||||
const requestService = new RequestChannel(accessor.get(IRequestService));
|
||||
sharedProcessClient.then(client => client.registerChannel('request', requestService));
|
||||
|
||||
// Update
|
||||
const updateChannel = new UpdateChannel(accessor.get(IUpdateService));
|
||||
mainProcessElectronServer.registerChannel('update', updateChannel);
|
||||
|
@ -1006,6 +1012,7 @@ export class CodeApplication extends Disposable {
|
|||
cli: args,
|
||||
forceNewWindow: args['new-window'] || (!hasCliArgs && args['unity-launch']),
|
||||
diffMode: args.diff,
|
||||
mergeMode: args.merge,
|
||||
noRecentEntry,
|
||||
waitMarkerFileURI,
|
||||
gotoLineMode: args.goto,
|
||||
|
|
|
@ -8,7 +8,7 @@ import { chmodSync, existsSync, readFileSync, statSync, truncateSync, unlinkSync
|
|||
import { homedir, release, tmpdir } from 'os';
|
||||
import type { ProfilingSession, Target } from 'v8-inspect-profiler';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { isAbsolute, resolve } from 'vs/base/common/path';
|
||||
import { isAbsolute, resolve, join } from 'vs/base/common/path';
|
||||
import { IProcessEnvironment, isMacintosh, isWindows } from 'vs/base/common/platform';
|
||||
import { randomPort } from 'vs/base/common/ports';
|
||||
import { isString } from 'vs/base/common/types';
|
||||
|
@ -24,6 +24,8 @@ import product from 'vs/platform/product/common/product';
|
|||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { randomPath } from 'vs/base/common/extpath';
|
||||
import { Utils } from 'vs/platform/profiling/common/profiling';
|
||||
import { dirname } from 'vs/base/common/resources';
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
|
||||
function shouldSpawnCliProcess(argv: NativeParsedArgs): boolean {
|
||||
return !!argv['install-source']
|
||||
|
@ -59,6 +61,25 @@ export async function main(argv: string[]): Promise<any> {
|
|||
console.log(buildVersionMessage(product.version, product.commit));
|
||||
}
|
||||
|
||||
// Shell integration
|
||||
else if (args['shell-integration']) {
|
||||
// Silently fail when the terminal is not VS Code's integrated terminal
|
||||
if (process.env['TERM_PROGRAM'] !== 'vscode') {
|
||||
return;
|
||||
}
|
||||
let file: string;
|
||||
switch (args['shell-integration']) {
|
||||
// Usage: `[[ "$TERM_PROGRAM" == "vscode" ]] && . "$(code --shell-integration bash)"`
|
||||
case 'bash': file = 'shellIntegration-bash.sh'; break;
|
||||
// Usage: `if ($env:TERM_PROGRAM -eq "vscode") { . "$(code --shell-integration pwsh)" }`
|
||||
case 'pwsh': file = 'shellIntegration.ps1'; break;
|
||||
// Usage: `[[ "$TERM_PROGRAM" == "vscode" ]] && . "$(code --shell-integration zsh)"`
|
||||
case 'zsh': file = 'shellIntegration-rc.zsh'; break;
|
||||
default: throw new Error('Error using --shell-integration: Invalid shell type');
|
||||
}
|
||||
console.log(join(dirname(FileAccess.asFileUri('', require)).fsPath, 'out', 'vs', 'workbench', 'contrib', 'terminal', 'browser', 'media', file));
|
||||
}
|
||||
|
||||
// Extensions Management
|
||||
else if (shouldSpawnCliProcess(args)) {
|
||||
const cli = await new Promise<IMainCli>((resolve, reject) => require(['vs/code/node/cliProcessMain'], resolve, reject));
|
||||
|
|
|
@ -339,8 +339,9 @@ export abstract class EditorAction extends EditorCommand {
|
|||
protected reportTelemetry(accessor: ServicesAccessor, editor: ICodeEditor) {
|
||||
type EditorActionInvokedClassification = {
|
||||
owner: 'alexdima';
|
||||
name: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
|
||||
id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
|
||||
comment: 'An editor action has been invoked.';
|
||||
name: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The label of the action that was invoked.' };
|
||||
id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the action that was invoked.' };
|
||||
};
|
||||
type EditorActionInvokedEvent = {
|
||||
name: string;
|
||||
|
|
|
@ -2966,7 +2966,7 @@ class EditorQuickSuggestions extends BaseEditorOption<EditorOption.quickSuggesti
|
|||
},
|
||||
},
|
||||
default: defaults,
|
||||
markdownDescription: nls.localize('quickSuggestions', "Controls whether suggestions should automatically show up while typing. This can be controlled for typing in comments, strings, and other code. Quick suggestion can be configured to show as ghost text or with the suggest widget.")
|
||||
markdownDescription: nls.localize('quickSuggestions', "Controls whether suggestions should automatically show up while typing. This can be controlled for typing in comments, strings, and other code. Quick suggestion can be configured to show as ghost text or with the suggest widget. Also be aware of the '{0}'-setting which controls if suggestions are triggered by special characters.", `#editor.suggestOnTriggerCharacters#`)
|
||||
});
|
||||
this.defaultValue = defaults;
|
||||
}
|
||||
|
@ -4557,7 +4557,7 @@ export const enum EditorOption {
|
|||
export const EditorOptions = {
|
||||
acceptSuggestionOnCommitCharacter: register(new EditorBooleanOption(
|
||||
EditorOption.acceptSuggestionOnCommitCharacter, 'acceptSuggestionOnCommitCharacter', true,
|
||||
{ markdownDescription: nls.localize('acceptSuggestionOnCommitCharacter', "Controls whether suggestions should be accepted on commit characters. For example, in JavaScript, the semi-colon (`;`) can be a commit character that accepts a suggestion and types that character.") }
|
||||
{ markdownDescription: nls.localize('acceptSuggestionOnCommitCharacter', "Controls whether suggestions should be accepted on commit characters. For example, in JavaScript, the semi-colon (`; `) can be a commit character that accepts a suggestion and types that character.") }
|
||||
)),
|
||||
acceptSuggestionOnEnter: register(new EditorStringEnumOption(
|
||||
EditorOption.acceptSuggestionOnEnter, 'acceptSuggestionOnEnter',
|
||||
|
|
|
@ -223,6 +223,10 @@ function collectBrackets(
|
|||
level: number,
|
||||
levelPerBracketType: Map<string, number>
|
||||
): void {
|
||||
if (level > 200) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.kind === AstNodeKind.List) {
|
||||
for (const child of node.children) {
|
||||
nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length);
|
||||
|
@ -333,6 +337,10 @@ function collectBracketPairs(
|
|||
level: number,
|
||||
levelPerBracketType: Map<string, number>
|
||||
) {
|
||||
if (level > 200) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.kind === AstNodeKind.Pair) {
|
||||
let levelPerBracket = 0;
|
||||
if (levelPerBracketType) {
|
||||
|
|
|
@ -46,7 +46,7 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
|
|||
this._languageFeaturesService.documentOnDropEditProvider.register('*', new DefaultOnDropProvider(workspaceContextService));
|
||||
|
||||
this._register(this._configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('workbench.experimental.editor.dropIntoEditor.enabled')) {
|
||||
if (e.affectsConfiguration('workbench.editor.dropIntoEditor.enabled')) {
|
||||
this.updateEditorOptions(editor);
|
||||
}
|
||||
}));
|
||||
|
@ -56,7 +56,7 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
|
|||
|
||||
private updateEditorOptions(editor: ICodeEditor) {
|
||||
editor.updateOptions({
|
||||
enableDropIntoEditor: this._configurationService.getValue('workbench.experimental.editor.dropIntoEditor.enabled')
|
||||
enableDropIntoEditor: this._configurationService.getValue('workbench.editor.dropIntoEditor.enabled')
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -102,7 +102,9 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget {
|
|||
}));
|
||||
|
||||
const body = $('.body');
|
||||
const scrollbar = new DomScrollableElement(body, {});
|
||||
const scrollbar = new DomScrollableElement(body, {
|
||||
alwaysConsumeMouseWheel: true,
|
||||
});
|
||||
this._register(scrollbar);
|
||||
wrapper.appendChild(scrollbar.getDomNode());
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue