mirror of
https://github.com/Microsoft/vscode
synced 2024-10-05 19:02:54 +00:00
Merge remote-tracking branch 'origin/main' into tyriar/persist_tests
This commit is contained in:
commit
ef440e563d
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
|
@ -85,7 +85,7 @@ jobs:
|
|||
timeout-minutes: 10
|
||||
run: .\resources\server\test\test-web-integration.bat --browser firefox
|
||||
|
||||
- name: Run Remote Integration Tests (Electron)
|
||||
- name: Run Integration Tests (Remote)
|
||||
timeout-minutes: 10
|
||||
run: .\resources\server\test\test-remote-integration.bat
|
||||
|
||||
|
@ -162,7 +162,7 @@ jobs:
|
|||
id: browser-integration-tests
|
||||
run: DISPLAY=:10 ./resources/server/test/test-web-integration.sh --browser chromium
|
||||
|
||||
- name: Run Remote Integration Tests (Electron)
|
||||
- name: Run Integration Tests (Remote)
|
||||
id: electron-remote-integration-tests
|
||||
timeout-minutes: 7
|
||||
run: DISPLAY=:10 ./resources/server/test/test-remote-integration.sh
|
||||
|
@ -234,7 +234,7 @@ jobs:
|
|||
- name: Run Integration Tests (Browser)
|
||||
run: DISPLAY=:10 ./resources/server/test/test-web-integration.sh --browser webkit
|
||||
|
||||
- name: Run Remote Integration Tests (Electron)
|
||||
- name: Run Integration Tests (Remote)
|
||||
timeout-minutes: 7
|
||||
run: DISPLAY=:10 ./resources/server/test/test-remote-integration.sh
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ steps:
|
|||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
SecretsFilter: 'github-distro-mixin-password,macos-developer-certificate,macos-developer-certificate-key,ticino-storage-key'
|
||||
SecretsFilter: "github-distro-mixin-password,macos-developer-certificate,macos-developer-certificate-key"
|
||||
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
|
@ -215,7 +215,7 @@ steps:
|
|||
INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \
|
||||
VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin" \
|
||||
./resources/server/test/test-remote-integration.sh
|
||||
displayName: Run remote integration tests (Electron)
|
||||
displayName: Run integration tests (Remote)
|
||||
timeoutInMinutes: 7
|
||||
condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
|
||||
|
||||
|
@ -301,10 +301,25 @@ steps:
|
|||
displayName: Publish web server archive
|
||||
condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), ne(variables['VSCODE_PUBLISH'], 'false'))
|
||||
|
||||
- task: AzureCLI@2
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
scriptType: pscore
|
||||
scriptLocation: inlineScript
|
||||
addSpnToEnvironment: true
|
||||
inlineScript: |
|
||||
Write-Host "##vso[task.setvariable variable=AZURE_TENANT_ID]$env:tenantId"
|
||||
Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_ID]$env:servicePrincipalId"
|
||||
Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_SECRET;issecret=true]$env:servicePrincipalKey"
|
||||
|
||||
- script: |
|
||||
AZURE_STORAGE_ACCESS_KEY="$(ticino-storage-key)" \
|
||||
set -e
|
||||
AZURE_STORAGE_ACCOUNT="ticino" \
|
||||
AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \
|
||||
AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \
|
||||
AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \
|
||||
VSCODE_ARCH="$(VSCODE_ARCH)" \
|
||||
yarn gulp upload-vscode-configuration
|
||||
node build/azure-pipelines/upload-configuration
|
||||
displayName: Upload configuration (for Bing settings search)
|
||||
condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), ne(variables['VSCODE_PUBLISH'], 'false'))
|
||||
continueOnError: true
|
||||
|
|
|
@ -18,7 +18,7 @@ steps:
|
|||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
SecretsFilter: 'github-distro-mixin-password'
|
||||
SecretsFilter: "github-distro-mixin-password"
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
|
|
|
@ -14,7 +14,7 @@ steps:
|
|||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
SecretsFilter: 'github-distro-mixin-password'
|
||||
SecretsFilter: "github-distro-mixin-password"
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
|
|
|
@ -8,7 +8,7 @@ steps:
|
|||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
SecretsFilter: "github-distro-mixin-password,builds-docdb-key-readwrite,vscode-storage-key,ESRP-PKI,esrp-aad-username,esrp-aad-password"
|
||||
SecretsFilter: "github-distro-mixin-password,ESRP-PKI,esrp-aad-username,esrp-aad-password"
|
||||
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
|
@ -200,7 +200,7 @@ steps:
|
|||
INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \
|
||||
VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \
|
||||
./resources/server/test/test-remote-integration.sh
|
||||
displayName: Run remote integration tests (Electron)
|
||||
displayName: Run integration tests (Remote)
|
||||
timeoutInMinutes: 7
|
||||
condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
|
||||
|
||||
|
|
|
@ -113,10 +113,6 @@ variables:
|
|||
value: https://az764295.vo.msecnd.net
|
||||
- name: AZURE_DOCUMENTDB_ENDPOINT
|
||||
value: https://vscode.documents.azure.com:443/
|
||||
- name: AZURE_STORAGE_ACCOUNT
|
||||
value: ticino
|
||||
- name: AZURE_STORAGE_ACCOUNT_2
|
||||
value: vscode
|
||||
- name: MOONCAKE_CDN_URL
|
||||
value: https://vscode.cdn.azure.cn
|
||||
- name: VSCODE_MIXIN_REPO
|
||||
|
|
|
@ -8,7 +8,7 @@ steps:
|
|||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
SecretsFilter: "github-distro-mixin-password,ticino-storage-key"
|
||||
SecretsFilter: "github-distro-mixin-password"
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
|
@ -103,9 +103,23 @@ steps:
|
|||
displayName: Compile test suites
|
||||
condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
|
||||
|
||||
- task: AzureCLI@2
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
scriptType: pscore
|
||||
scriptLocation: inlineScript
|
||||
addSpnToEnvironment: true
|
||||
inlineScript: |
|
||||
Write-Host "##vso[task.setvariable variable=AZURE_TENANT_ID]$env:tenantId"
|
||||
Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_ID]$env:servicePrincipalId"
|
||||
Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_SECRET;issecret=true]$env:servicePrincipalKey"
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
AZURE_STORAGE_ACCESS_KEY="$(ticino-storage-key)" \
|
||||
AZURE_STORAGE_ACCOUNT="ticino" \
|
||||
AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \
|
||||
AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \
|
||||
AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \
|
||||
node build/azure-pipelines/upload-sourcemaps
|
||||
displayName: Upload sourcemaps
|
||||
condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false'))
|
||||
|
|
|
@ -8,7 +8,7 @@ steps:
|
|||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
SecretsFilter: "builds-docdb-key-readwrite,github-distro-mixin-password,ticino-storage-key,vscode-storage-key,vscode-mooncake-storage-key"
|
||||
SecretsFilter: "github-distro-mixin-password"
|
||||
|
||||
- pwsh: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
|
|
|
@ -32,201 +32,201 @@ variables:
|
|||
value: x64
|
||||
|
||||
stages:
|
||||
- stage: Windows
|
||||
condition: eq(variables.SCAN_WINDOWS, 'true')
|
||||
pool:
|
||||
vmImage: VS2017-Win2016
|
||||
jobs:
|
||||
- job: WindowsJob
|
||||
timeoutInMinutes: 0
|
||||
steps:
|
||||
- task: CredScan@3
|
||||
continueOnError: true
|
||||
inputs:
|
||||
scanFolder: '$(Build.SourcesDirectory)'
|
||||
outputFormat: 'pre'
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: "14.x"
|
||||
- stage: Windows
|
||||
condition: eq(variables.SCAN_WINDOWS, 'true')
|
||||
pool:
|
||||
vmImage: VS2017-Win2016
|
||||
jobs:
|
||||
- job: WindowsJob
|
||||
timeoutInMinutes: 0
|
||||
steps:
|
||||
- task: CredScan@3
|
||||
continueOnError: true
|
||||
inputs:
|
||||
scanFolder: "$(Build.SourcesDirectory)"
|
||||
outputFormat: "pre"
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: "14.x"
|
||||
|
||||
- task: AzureKeyVault@1
|
||||
displayName: "Azure Key Vault: Get Secrets"
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
SecretsFilter: "github-distro-mixin-password"
|
||||
- task: AzureKeyVault@1
|
||||
displayName: "Azure Key Vault: Get Secrets"
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
SecretsFilter: "github-distro-mixin-password"
|
||||
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
"machine github.com`nlogin vscode`npassword $(github-distro-mixin-password)" | Out-File "$env:USERPROFILE\_netrc" -Encoding ASCII
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
"machine github.com`nlogin vscode`npassword $(github-distro-mixin-password)" | Out-File "$env:USERPROFILE\_netrc" -Encoding ASCII
|
||||
|
||||
exec { git config user.email "vscode@microsoft.com" }
|
||||
exec { git config user.name "VSCode" }
|
||||
displayName: Prepare tooling
|
||||
exec { git config user.email "vscode@microsoft.com" }
|
||||
exec { git config user.name "VSCode" }
|
||||
displayName: Prepare tooling
|
||||
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
exec { git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") }
|
||||
displayName: Merge distro
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
exec { git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") }
|
||||
displayName: Merge distro
|
||||
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
exec { npx https://aka.ms/enablesecurefeed standAlone }
|
||||
timeoutInMinutes: 5
|
||||
condition: and(succeeded(), eq(variables['ENABLE_TERRAPIN'], 'true'))
|
||||
displayName: Switch to Terrapin packages
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
exec { npx https://aka.ms/enablesecurefeed standAlone }
|
||||
timeoutInMinutes: 5
|
||||
condition: and(succeeded(), eq(variables['ENABLE_TERRAPIN'], 'true'))
|
||||
displayName: Switch to Terrapin packages
|
||||
|
||||
- task: Semmle@1
|
||||
inputs:
|
||||
sourceCodeDirectory: '$(Build.SourcesDirectory)'
|
||||
language: 'cpp'
|
||||
buildCommandsString: 'yarn --frozen-lockfile'
|
||||
querySuite: 'Required'
|
||||
timeout: '1800'
|
||||
ram: '16384'
|
||||
addProjectDirToScanningExclusionList: true
|
||||
env:
|
||||
npm_config_arch: "$(NPM_ARCH)"
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
GITHUB_TOKEN: "$(github-distro-mixin-password)"
|
||||
displayName: CodeQL
|
||||
- task: Semmle@1
|
||||
inputs:
|
||||
sourceCodeDirectory: "$(Build.SourcesDirectory)"
|
||||
language: "cpp"
|
||||
buildCommandsString: "yarn --frozen-lockfile"
|
||||
querySuite: "Required"
|
||||
timeout: "1800"
|
||||
ram: "16384"
|
||||
addProjectDirToScanningExclusionList: true
|
||||
env:
|
||||
npm_config_arch: "$(NPM_ARCH)"
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
GITHUB_TOKEN: "$(github-distro-mixin-password)"
|
||||
displayName: CodeQL
|
||||
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
. build/azure-pipelines/win32/retry.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
retry { exec { yarn --frozen-lockfile } }
|
||||
env:
|
||||
npm_config_arch: "$(NPM_ARCH)"
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
GITHUB_TOKEN: "$(github-distro-mixin-password)"
|
||||
CHILD_CONCURRENCY: 1
|
||||
displayName: Install dependencies
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
. build/azure-pipelines/win32/retry.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
retry { exec { yarn --frozen-lockfile } }
|
||||
env:
|
||||
npm_config_arch: "$(NPM_ARCH)"
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
GITHUB_TOKEN: "$(github-distro-mixin-password)"
|
||||
CHILD_CONCURRENCY: 1
|
||||
displayName: Install dependencies
|
||||
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
exec { yarn gulp "vscode-symbols-win32-$(VSCODE_ARCH)" }
|
||||
displayName: Download Symbols
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
exec { yarn gulp "vscode-symbols-win32-$(VSCODE_ARCH)" }
|
||||
displayName: Download Symbols
|
||||
|
||||
- task: BinSkim@4
|
||||
inputs:
|
||||
InputType: 'Basic'
|
||||
Function: 'analyze'
|
||||
TargetPattern: 'guardianGlob'
|
||||
AnalyzeTargetGlob: '$(agent.builddirectory)\scanbin\**.dll;$(agent.builddirectory)\scanbin\**.exe;$(agent.builddirectory)\scanbin\**.node'
|
||||
AnalyzeLocalSymbolDirectories: '$(agent.builddirectory)\scanbin\VSCode-win32-$(VSCODE_ARCH)\pdb'
|
||||
- task: BinSkim@4
|
||||
inputs:
|
||||
InputType: "Basic"
|
||||
Function: "analyze"
|
||||
TargetPattern: "guardianGlob"
|
||||
AnalyzeTargetGlob: '$(agent.builddirectory)\scanbin\**.dll;$(agent.builddirectory)\scanbin\**.exe;$(agent.builddirectory)\scanbin\**.node'
|
||||
AnalyzeLocalSymbolDirectories: '$(agent.builddirectory)\scanbin\VSCode-win32-$(VSCODE_ARCH)\pdb'
|
||||
|
||||
- task: TSAUpload@2
|
||||
inputs:
|
||||
GdnPublishTsaOnboard: true
|
||||
GdnPublishTsaConfigFile: '$(Build.SourcesDirectory)\build\azure-pipelines\.gdntsa'
|
||||
- task: TSAUpload@2
|
||||
inputs:
|
||||
GdnPublishTsaOnboard: true
|
||||
GdnPublishTsaConfigFile: '$(Build.SourcesDirectory)\build\azure-pipelines\.gdntsa'
|
||||
|
||||
- stage: Linux
|
||||
dependsOn: []
|
||||
condition: eq(variables.SCAN_LINUX, 'true')
|
||||
pool:
|
||||
vmImage: "Ubuntu-18.04"
|
||||
jobs:
|
||||
- job: LinuxJob
|
||||
steps:
|
||||
- task: CredScan@2
|
||||
inputs:
|
||||
toolMajorVersion: 'V2'
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: "14.x"
|
||||
- stage: Linux
|
||||
dependsOn: []
|
||||
condition: eq(variables.SCAN_LINUX, 'true')
|
||||
pool:
|
||||
vmImage: "Ubuntu-18.04"
|
||||
jobs:
|
||||
- job: LinuxJob
|
||||
steps:
|
||||
- task: CredScan@2
|
||||
inputs:
|
||||
toolMajorVersion: "V2"
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: "14.x"
|
||||
|
||||
- task: AzureKeyVault@1
|
||||
displayName: "Azure Key Vault: Get Secrets"
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
SecretsFilter: "github-distro-mixin-password"
|
||||
- task: AzureKeyVault@1
|
||||
displayName: "Azure Key Vault: Get Secrets"
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
SecretsFilter: "github-distro-mixin-password"
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
cat << EOF > ~/.netrc
|
||||
machine github.com
|
||||
login vscode
|
||||
password $(github-distro-mixin-password)
|
||||
EOF
|
||||
- script: |
|
||||
set -e
|
||||
cat << EOF > ~/.netrc
|
||||
machine github.com
|
||||
login vscode
|
||||
password $(github-distro-mixin-password)
|
||||
EOF
|
||||
|
||||
git config user.email "vscode@microsoft.com"
|
||||
git config user.name "VSCode"
|
||||
displayName: Prepare tooling
|
||||
git config user.email "vscode@microsoft.com"
|
||||
git config user.name "VSCode"
|
||||
displayName: Prepare tooling
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro")
|
||||
displayName: Merge distro
|
||||
- script: |
|
||||
set -e
|
||||
git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro")
|
||||
displayName: Merge distro
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
npx https://aka.ms/enablesecurefeed standAlone
|
||||
timeoutInMinutes: 5
|
||||
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['ENABLE_TERRAPIN'], 'true'))
|
||||
displayName: Switch to Terrapin packages
|
||||
- script: |
|
||||
set -e
|
||||
npx https://aka.ms/enablesecurefeed standAlone
|
||||
timeoutInMinutes: 5
|
||||
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['ENABLE_TERRAPIN'], 'true'))
|
||||
displayName: Switch to Terrapin packages
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
yarn --cwd build
|
||||
yarn --cwd build compile
|
||||
displayName: Compile build tools
|
||||
- script: |
|
||||
set -e
|
||||
yarn --cwd build
|
||||
yarn --cwd build compile
|
||||
displayName: Compile build tools
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
export npm_config_arch=$(NPM_ARCH)
|
||||
- script: |
|
||||
set -e
|
||||
export npm_config_arch=$(NPM_ARCH)
|
||||
|
||||
if [ -z "$CC" ] || [ -z "$CXX" ]; then
|
||||
# Download clang based on chromium revision used by vscode
|
||||
curl -s https://raw.githubusercontent.com/chromium/chromium/91.0.4472.164/tools/clang/scripts/update.py | python - --output-dir=$PWD/.build/CR_Clang --host-os=linux
|
||||
# Download libcxx headers and objects from upstream electron releases
|
||||
DEBUG=libcxx-fetcher \
|
||||
VSCODE_LIBCXX_OBJECTS_DIR=$PWD/.build/libcxx-objects \
|
||||
VSCODE_LIBCXX_HEADERS_DIR=$PWD/.build/libcxx_headers \
|
||||
VSCODE_LIBCXXABI_HEADERS_DIR=$PWD/.build/libcxxabi_headers \
|
||||
VSCODE_ARCH="$(NPM_ARCH)" \
|
||||
node build/linux/libcxx-fetcher.js
|
||||
# Set compiler toolchain
|
||||
export CC=$PWD/.build/CR_Clang/bin/clang
|
||||
export CXX=$PWD/.build/CR_Clang/bin/clang++
|
||||
export CXXFLAGS="-nostdinc++ -D_LIBCPP_HAS_NO_VENDOR_AVAILABILITY_ANNOTATIONS -isystem$PWD/.build/libcxx_headers/include -isystem$PWD/.build/libcxxabi_headers/include -fPIC -flto=thin -fsplit-lto-unit"
|
||||
export LDFLAGS="-stdlib=libc++ -fuse-ld=lld -flto=thin -fsplit-lto-unit -L$PWD/.build/libcxx-objects -lc++abi"
|
||||
fi
|
||||
if [ -z "$CC" ] || [ -z "$CXX" ]; then
|
||||
# Download clang based on chromium revision used by vscode
|
||||
curl -s https://raw.githubusercontent.com/chromium/chromium/91.0.4472.164/tools/clang/scripts/update.py | python - --output-dir=$PWD/.build/CR_Clang --host-os=linux
|
||||
# Download libcxx headers and objects from upstream electron releases
|
||||
DEBUG=libcxx-fetcher \
|
||||
VSCODE_LIBCXX_OBJECTS_DIR=$PWD/.build/libcxx-objects \
|
||||
VSCODE_LIBCXX_HEADERS_DIR=$PWD/.build/libcxx_headers \
|
||||
VSCODE_LIBCXXABI_HEADERS_DIR=$PWD/.build/libcxxabi_headers \
|
||||
VSCODE_ARCH="$(NPM_ARCH)" \
|
||||
node build/linux/libcxx-fetcher.js
|
||||
# Set compiler toolchain
|
||||
export CC=$PWD/.build/CR_Clang/bin/clang
|
||||
export CXX=$PWD/.build/CR_Clang/bin/clang++
|
||||
export CXXFLAGS="-nostdinc++ -D_LIBCPP_HAS_NO_VENDOR_AVAILABILITY_ANNOTATIONS -isystem$PWD/.build/libcxx_headers/include -isystem$PWD/.build/libcxxabi_headers/include -fPIC -flto=thin -fsplit-lto-unit"
|
||||
export LDFLAGS="-stdlib=libc++ -fuse-ld=lld -flto=thin -fsplit-lto-unit -L$PWD/.build/libcxx-objects -lc++abi"
|
||||
fi
|
||||
|
||||
if [ "$VSCODE_ARCH" == "x64" ]; then
|
||||
export VSCODE_REMOTE_CC=$(which gcc-4.8)
|
||||
export VSCODE_REMOTE_CXX=$(which g++-4.8)
|
||||
fi
|
||||
if [ "$VSCODE_ARCH" == "x64" ]; then
|
||||
export VSCODE_REMOTE_CC=$(which gcc-4.8)
|
||||
export VSCODE_REMOTE_CXX=$(which g++-4.8)
|
||||
fi
|
||||
|
||||
for i in {1..3}; do # try 3 times, for Terrapin
|
||||
yarn --frozen-lockfile && break
|
||||
if [ $i -eq 3 ]; then
|
||||
echo "Yarn failed too many times" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Yarn failed $i, trying again..."
|
||||
done
|
||||
env:
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
GITHUB_TOKEN: "$(github-distro-mixin-password)"
|
||||
displayName: Install dependencies
|
||||
for i in {1..3}; do # try 3 times, for Terrapin
|
||||
yarn --frozen-lockfile && break
|
||||
if [ $i -eq 3 ]; then
|
||||
echo "Yarn failed too many times" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Yarn failed $i, trying again..."
|
||||
done
|
||||
env:
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
GITHUB_TOKEN: "$(github-distro-mixin-password)"
|
||||
displayName: Install dependencies
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
yarn gulp vscode-symbols-linux-$(VSCODE_ARCH)
|
||||
displayName: Build
|
||||
- script: |
|
||||
set -e
|
||||
yarn gulp vscode-symbols-linux-$(VSCODE_ARCH)
|
||||
displayName: Build
|
||||
|
||||
- task: BinSkim@3
|
||||
inputs:
|
||||
toolVersion: Latest
|
||||
InputType: CommandLine
|
||||
arguments: analyze $(agent.builddirectory)\scanbin\exe\*.* --recurse --local-symbol-directories $(agent.builddirectory)\scanbin\VSCode-linux-$(VSCODE_ARCH)\pdb
|
||||
- task: BinSkim@3
|
||||
inputs:
|
||||
toolVersion: Latest
|
||||
InputType: CommandLine
|
||||
arguments: analyze $(agent.builddirectory)\scanbin\exe\*.* --recurse --local-symbol-directories $(agent.builddirectory)\scanbin\VSCode-linux-$(VSCODE_ARCH)\pdb
|
||||
|
||||
- task: TSAUpload@2
|
||||
inputs:
|
||||
GdnPublishTsaConfigFile: '$(Build.SourceDirectory)\build\azure-pipelines\.gdntsa'
|
||||
- task: TSAUpload@2
|
||||
inputs:
|
||||
GdnPublishTsaConfigFile: '$(Build.SourceDirectory)\build\azure-pipelines\.gdntsa'
|
||||
|
|
|
@ -10,26 +10,35 @@ const vfs = require("vinyl-fs");
|
|||
const util = require("../lib/util");
|
||||
const filter = require("gulp-filter");
|
||||
const gzip = require("gulp-gzip");
|
||||
const identity_1 = require("@azure/identity");
|
||||
const azure = require('gulp-azure-storage');
|
||||
const root = path.dirname(path.dirname(__dirname));
|
||||
const commit = util.getVersion(root);
|
||||
const credential = new identity_1.ClientSecretCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], process.env['AZURE_CLIENT_SECRET']);
|
||||
function main() {
|
||||
return vfs.src('**', { cwd: '../vscode-web', base: '../vscode-web', dot: true })
|
||||
.pipe(filter(f => !f.isDirectory()))
|
||||
.pipe(gzip({ append: false }))
|
||||
.pipe(es.through(function (data) {
|
||||
console.log('Uploading CDN file:', data.relative); // debug
|
||||
this.emit('data', data);
|
||||
}))
|
||||
.pipe(azure.upload({
|
||||
account: process.env.AZURE_STORAGE_ACCOUNT,
|
||||
key: process.env.AZURE_STORAGE_ACCESS_KEY,
|
||||
container: process.env.VSCODE_QUALITY,
|
||||
prefix: commit + '/',
|
||||
contentSettings: {
|
||||
contentEncoding: 'gzip',
|
||||
cacheControl: 'max-age=31536000, public'
|
||||
}
|
||||
}));
|
||||
return new Promise((c, e) => {
|
||||
vfs.src('**', { cwd: '../vscode-web', base: '../vscode-web', dot: true })
|
||||
.pipe(filter(f => !f.isDirectory()))
|
||||
.pipe(gzip({ append: false }))
|
||||
.pipe(es.through(function (data) {
|
||||
console.log('Uploading CDN file:', data.relative); // debug
|
||||
this.emit('data', data);
|
||||
}))
|
||||
.pipe(azure.upload({
|
||||
account: process.env.AZURE_STORAGE_ACCOUNT,
|
||||
credential,
|
||||
container: process.env.VSCODE_QUALITY,
|
||||
prefix: commit + '/',
|
||||
contentSettings: {
|
||||
contentEncoding: 'gzip',
|
||||
cacheControl: 'max-age=31536000, public'
|
||||
}
|
||||
}))
|
||||
.on('end', () => c())
|
||||
.on('error', (err) => e(err));
|
||||
});
|
||||
}
|
||||
main();
|
||||
main().catch(err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
|
@ -12,29 +12,38 @@ import * as vfs from 'vinyl-fs';
|
|||
import * as util from '../lib/util';
|
||||
import * as filter from 'gulp-filter';
|
||||
import * as gzip from 'gulp-gzip';
|
||||
import { ClientSecretCredential } from '@azure/identity';
|
||||
const azure = require('gulp-azure-storage');
|
||||
|
||||
const root = path.dirname(path.dirname(__dirname));
|
||||
const commit = util.getVersion(root);
|
||||
const credential = new ClientSecretCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, process.env['AZURE_CLIENT_SECRET']!);
|
||||
|
||||
function main() {
|
||||
return vfs.src('**', { cwd: '../vscode-web', base: '../vscode-web', dot: true })
|
||||
.pipe(filter(f => !f.isDirectory()))
|
||||
.pipe(gzip({ append: false }))
|
||||
.pipe(es.through(function (data: Vinyl) {
|
||||
console.log('Uploading CDN file:', data.relative); // debug
|
||||
this.emit('data', data);
|
||||
}))
|
||||
.pipe(azure.upload({
|
||||
account: process.env.AZURE_STORAGE_ACCOUNT,
|
||||
key: process.env.AZURE_STORAGE_ACCESS_KEY,
|
||||
container: process.env.VSCODE_QUALITY,
|
||||
prefix: commit + '/',
|
||||
contentSettings: {
|
||||
contentEncoding: 'gzip',
|
||||
cacheControl: 'max-age=31536000, public'
|
||||
}
|
||||
}));
|
||||
function main(): Promise<void> {
|
||||
return new Promise((c, e) => {
|
||||
vfs.src('**', { cwd: '../vscode-web', base: '../vscode-web', dot: true })
|
||||
.pipe(filter(f => !f.isDirectory()))
|
||||
.pipe(gzip({ append: false }))
|
||||
.pipe(es.through(function (data: Vinyl) {
|
||||
console.log('Uploading CDN file:', data.relative); // debug
|
||||
this.emit('data', data);
|
||||
}))
|
||||
.pipe(azure.upload({
|
||||
account: process.env.AZURE_STORAGE_ACCOUNT,
|
||||
credential,
|
||||
container: process.env.VSCODE_QUALITY,
|
||||
prefix: commit + '/',
|
||||
contentSettings: {
|
||||
contentEncoding: 'gzip',
|
||||
cacheControl: 'max-age=31536000, public'
|
||||
}
|
||||
}))
|
||||
.on('end', () => c())
|
||||
.on('error', (err: any) => e(err));
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
main().catch(err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
112
build/azure-pipelines/upload-configuration.js
Normal file
112
build/azure-pipelines/upload-configuration.js
Normal file
|
@ -0,0 +1,112 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getSettingsSearchBuildId = exports.shouldSetupSettingsSearch = void 0;
|
||||
const path = require("path");
|
||||
const os = require("os");
|
||||
const cp = require("child_process");
|
||||
const vfs = require("vinyl-fs");
|
||||
const util = require("../lib/util");
|
||||
const identity_1 = require("@azure/identity");
|
||||
const azure = require('gulp-azure-storage');
|
||||
const packageJson = require("../../package.json");
|
||||
const root = path.dirname(path.dirname(__dirname));
|
||||
const commit = util.getVersion(root);
|
||||
function generateVSCodeConfigurationTask() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const buildDir = process.env['AGENT_BUILDDIRECTORY'];
|
||||
if (!buildDir) {
|
||||
return reject(new Error('$AGENT_BUILDDIRECTORY not set'));
|
||||
}
|
||||
if (!shouldSetupSettingsSearch()) {
|
||||
console.log(`Only runs on main and release branches, not ${process.env.BUILD_SOURCEBRANCH}`);
|
||||
return resolve(undefined);
|
||||
}
|
||||
if (process.env.VSCODE_QUALITY !== 'insider' && process.env.VSCODE_QUALITY !== 'stable') {
|
||||
console.log(`Only runs on insider and stable qualities, not ${process.env.VSCODE_QUALITY}`);
|
||||
return resolve(undefined);
|
||||
}
|
||||
const result = path.join(os.tmpdir(), 'configuration.json');
|
||||
const userDataDir = path.join(os.tmpdir(), 'tmpuserdata');
|
||||
const extensionsDir = path.join(os.tmpdir(), 'tmpextdir');
|
||||
const arch = process.env['VSCODE_ARCH'];
|
||||
const appRoot = path.join(buildDir, `VSCode-darwin-${arch}`);
|
||||
const appName = process.env.VSCODE_QUALITY === 'insider' ? 'Visual\\ Studio\\ Code\\ -\\ Insiders.app' : 'Visual\\ Studio\\ Code.app';
|
||||
const appPath = path.join(appRoot, appName, 'Contents', 'Resources', 'app', 'bin', 'code');
|
||||
const codeProc = cp.exec(`${appPath} --export-default-configuration='${result}' --wait --user-data-dir='${userDataDir}' --extensions-dir='${extensionsDir}'`, (err, stdout, stderr) => {
|
||||
clearTimeout(timer);
|
||||
if (err) {
|
||||
console.log(`err: ${err} ${err.message} ${err.toString()}`);
|
||||
reject(err);
|
||||
}
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
if (stderr) {
|
||||
console.log(`stderr: ${stderr}`);
|
||||
}
|
||||
resolve(result);
|
||||
});
|
||||
const timer = setTimeout(() => {
|
||||
codeProc.kill();
|
||||
reject(new Error('export-default-configuration process timed out'));
|
||||
}, 12 * 1000);
|
||||
codeProc.on('error', err => {
|
||||
clearTimeout(timer);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
function shouldSetupSettingsSearch() {
|
||||
const branch = process.env.BUILD_SOURCEBRANCH;
|
||||
return !!(branch && (/\/main$/.test(branch) || branch.indexOf('/release/') >= 0));
|
||||
}
|
||||
exports.shouldSetupSettingsSearch = shouldSetupSettingsSearch;
|
||||
function getSettingsSearchBuildId(packageJson) {
|
||||
try {
|
||||
const branch = process.env.BUILD_SOURCEBRANCH;
|
||||
const branchId = branch.indexOf('/release/') >= 0 ? 0 :
|
||||
/\/main$/.test(branch) ? 1 :
|
||||
2; // Some unexpected branch
|
||||
const out = cp.execSync(`git rev-list HEAD --count`);
|
||||
const count = parseInt(out.toString());
|
||||
// <version number><commit count><branchId (avoid unlikely conflicts)>
|
||||
// 1.25.1, 1,234,567 commits, main = 1250112345671
|
||||
return util.versionStringToNumber(packageJson.version) * 1e8 + count * 10 + branchId;
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error('Could not determine build number: ' + e.toString());
|
||||
}
|
||||
}
|
||||
exports.getSettingsSearchBuildId = getSettingsSearchBuildId;
|
||||
async function main() {
|
||||
const configPath = await generateVSCodeConfigurationTask();
|
||||
if (!configPath) {
|
||||
return;
|
||||
}
|
||||
const settingsSearchBuildId = getSettingsSearchBuildId(packageJson);
|
||||
if (!settingsSearchBuildId) {
|
||||
throw new Error('Failed to compute build number');
|
||||
}
|
||||
const credential = new identity_1.ClientSecretCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], process.env['AZURE_CLIENT_SECRET']);
|
||||
return new Promise((c, e) => {
|
||||
vfs.src(configPath)
|
||||
.pipe(azure.upload({
|
||||
account: process.env.AZURE_STORAGE_ACCOUNT,
|
||||
credential,
|
||||
container: 'configuration',
|
||||
prefix: `${settingsSearchBuildId}/${commit}/`
|
||||
}))
|
||||
.on('end', () => c())
|
||||
.on('error', (err) => e(err));
|
||||
});
|
||||
}
|
||||
if (require.main === module) {
|
||||
main().catch(err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
132
build/azure-pipelines/upload-configuration.ts
Normal file
132
build/azure-pipelines/upload-configuration.ts
Normal file
|
@ -0,0 +1,132 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import * as cp from 'child_process';
|
||||
import * as vfs from 'vinyl-fs';
|
||||
import * as util from '../lib/util';
|
||||
import { ClientSecretCredential } from '@azure/identity';
|
||||
const azure = require('gulp-azure-storage');
|
||||
import * as packageJson from '../../package.json';
|
||||
|
||||
const root = path.dirname(path.dirname(__dirname));
|
||||
const commit = util.getVersion(root);
|
||||
|
||||
function generateVSCodeConfigurationTask(): Promise<string | undefined> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const buildDir = process.env['AGENT_BUILDDIRECTORY'];
|
||||
if (!buildDir) {
|
||||
return reject(new Error('$AGENT_BUILDDIRECTORY not set'));
|
||||
}
|
||||
|
||||
if (!shouldSetupSettingsSearch()) {
|
||||
console.log(`Only runs on main and release branches, not ${process.env.BUILD_SOURCEBRANCH}`);
|
||||
return resolve(undefined);
|
||||
}
|
||||
|
||||
if (process.env.VSCODE_QUALITY !== 'insider' && process.env.VSCODE_QUALITY !== 'stable') {
|
||||
console.log(`Only runs on insider and stable qualities, not ${process.env.VSCODE_QUALITY}`);
|
||||
return resolve(undefined);
|
||||
}
|
||||
|
||||
const result = path.join(os.tmpdir(), 'configuration.json');
|
||||
const userDataDir = path.join(os.tmpdir(), 'tmpuserdata');
|
||||
const extensionsDir = path.join(os.tmpdir(), 'tmpextdir');
|
||||
const arch = process.env['VSCODE_ARCH'];
|
||||
const appRoot = path.join(buildDir, `VSCode-darwin-${arch}`);
|
||||
const appName = process.env.VSCODE_QUALITY === 'insider' ? 'Visual\\ Studio\\ Code\\ -\\ Insiders.app' : 'Visual\\ Studio\\ Code.app';
|
||||
const appPath = path.join(appRoot, appName, 'Contents', 'Resources', 'app', 'bin', 'code');
|
||||
const codeProc = cp.exec(
|
||||
`${appPath} --export-default-configuration='${result}' --wait --user-data-dir='${userDataDir}' --extensions-dir='${extensionsDir}'`,
|
||||
(err, stdout, stderr) => {
|
||||
clearTimeout(timer);
|
||||
if (err) {
|
||||
console.log(`err: ${err} ${err.message} ${err.toString()}`);
|
||||
reject(err);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.log(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
}
|
||||
);
|
||||
const timer = setTimeout(() => {
|
||||
codeProc.kill();
|
||||
reject(new Error('export-default-configuration process timed out'));
|
||||
}, 12 * 1000);
|
||||
|
||||
codeProc.on('error', err => {
|
||||
clearTimeout(timer);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function shouldSetupSettingsSearch(): boolean {
|
||||
const branch = process.env.BUILD_SOURCEBRANCH;
|
||||
return !!(branch && (/\/main$/.test(branch) || branch.indexOf('/release/') >= 0));
|
||||
}
|
||||
|
||||
export function getSettingsSearchBuildId(packageJson: { version: string }) {
|
||||
try {
|
||||
const branch = process.env.BUILD_SOURCEBRANCH!;
|
||||
const branchId = branch.indexOf('/release/') >= 0 ? 0 :
|
||||
/\/main$/.test(branch) ? 1 :
|
||||
2; // Some unexpected branch
|
||||
|
||||
const out = cp.execSync(`git rev-list HEAD --count`);
|
||||
const count = parseInt(out.toString());
|
||||
|
||||
// <version number><commit count><branchId (avoid unlikely conflicts)>
|
||||
// 1.25.1, 1,234,567 commits, main = 1250112345671
|
||||
return util.versionStringToNumber(packageJson.version) * 1e8 + count * 10 + branchId;
|
||||
} catch (e) {
|
||||
throw new Error('Could not determine build number: ' + e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const configPath = await generateVSCodeConfigurationTask();
|
||||
|
||||
if (!configPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const settingsSearchBuildId = getSettingsSearchBuildId(packageJson);
|
||||
|
||||
if (!settingsSearchBuildId) {
|
||||
throw new Error('Failed to compute build number');
|
||||
}
|
||||
|
||||
const credential = new ClientSecretCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, process.env['AZURE_CLIENT_SECRET']!);
|
||||
|
||||
return new Promise((c, e) => {
|
||||
vfs.src(configPath)
|
||||
.pipe(azure.upload({
|
||||
account: process.env.AZURE_STORAGE_ACCOUNT,
|
||||
credential,
|
||||
container: 'configuration',
|
||||
prefix: `${settingsSearchBuildId}/${commit}/`
|
||||
}))
|
||||
.on('end', () => c())
|
||||
.on('error', (err: any) => e(err));
|
||||
});
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main().catch(err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
|
@ -10,79 +10,88 @@ const vfs = require("vinyl-fs");
|
|||
const util = require("../lib/util");
|
||||
const merge = require("gulp-merge-json");
|
||||
const gzip = require("gulp-gzip");
|
||||
const identity_1 = require("@azure/identity");
|
||||
const azure = require('gulp-azure-storage');
|
||||
const root = path.dirname(path.dirname(__dirname));
|
||||
const commit = util.getVersion(root);
|
||||
const credential = new identity_1.ClientSecretCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], process.env['AZURE_CLIENT_SECRET']);
|
||||
function main() {
|
||||
return es.merge(vfs.src('out-vscode-web-min/nls.metadata.json', { base: 'out-vscode-web-min' }), vfs.src('.build/extensions/**/nls.metadata.json', { base: '.build/extensions' }), vfs.src('.build/extensions/**/nls.metadata.header.json', { base: '.build/extensions' }), vfs.src('.build/extensions/**/package.nls.json', { base: '.build/extensions' }))
|
||||
.pipe(merge({
|
||||
fileName: 'combined.nls.metadata.json',
|
||||
jsonSpace: '',
|
||||
edit: (parsedJson, file) => {
|
||||
let key;
|
||||
if (file.base === 'out-vscode-web-min') {
|
||||
return { vscode: parsedJson };
|
||||
}
|
||||
// Handle extensions and follow the same structure as the Core nls file.
|
||||
switch (file.basename) {
|
||||
case 'package.nls.json':
|
||||
// put package.nls.json content in Core NlsMetadata format
|
||||
// language packs use the key "package" to specify that
|
||||
// translations are for the package.json file
|
||||
parsedJson = {
|
||||
messages: {
|
||||
package: Object.values(parsedJson)
|
||||
},
|
||||
keys: {
|
||||
package: Object.keys(parsedJson)
|
||||
},
|
||||
bundles: {
|
||||
main: ['package']
|
||||
return new Promise((c, e) => {
|
||||
es.merge(vfs.src('out-vscode-web-min/nls.metadata.json', { base: 'out-vscode-web-min' }), vfs.src('.build/extensions/**/nls.metadata.json', { base: '.build/extensions' }), vfs.src('.build/extensions/**/nls.metadata.header.json', { base: '.build/extensions' }), vfs.src('.build/extensions/**/package.nls.json', { base: '.build/extensions' }))
|
||||
.pipe(merge({
|
||||
fileName: 'combined.nls.metadata.json',
|
||||
jsonSpace: '',
|
||||
edit: (parsedJson, file) => {
|
||||
let key;
|
||||
if (file.base === 'out-vscode-web-min') {
|
||||
return { vscode: parsedJson };
|
||||
}
|
||||
// Handle extensions and follow the same structure as the Core nls file.
|
||||
switch (file.basename) {
|
||||
case 'package.nls.json':
|
||||
// put package.nls.json content in Core NlsMetadata format
|
||||
// language packs use the key "package" to specify that
|
||||
// translations are for the package.json file
|
||||
parsedJson = {
|
||||
messages: {
|
||||
package: Object.values(parsedJson)
|
||||
},
|
||||
keys: {
|
||||
package: Object.keys(parsedJson)
|
||||
},
|
||||
bundles: {
|
||||
main: ['package']
|
||||
}
|
||||
};
|
||||
break;
|
||||
case 'nls.metadata.header.json':
|
||||
parsedJson = { header: parsedJson };
|
||||
break;
|
||||
case 'nls.metadata.json':
|
||||
// put nls.metadata.json content in Core NlsMetadata format
|
||||
const modules = Object.keys(parsedJson);
|
||||
const json = {
|
||||
keys: {},
|
||||
messages: {},
|
||||
bundles: {
|
||||
main: []
|
||||
}
|
||||
};
|
||||
for (const module of modules) {
|
||||
json.messages[module] = parsedJson[module].messages;
|
||||
json.keys[module] = parsedJson[module].keys;
|
||||
json.bundles.main.push(module);
|
||||
}
|
||||
};
|
||||
break;
|
||||
case 'nls.metadata.header.json':
|
||||
parsedJson = { header: parsedJson };
|
||||
break;
|
||||
case 'nls.metadata.json':
|
||||
// put nls.metadata.json content in Core NlsMetadata format
|
||||
const modules = Object.keys(parsedJson);
|
||||
const json = {
|
||||
keys: {},
|
||||
messages: {},
|
||||
bundles: {
|
||||
main: []
|
||||
}
|
||||
};
|
||||
for (const module of modules) {
|
||||
json.messages[module] = parsedJson[module].messages;
|
||||
json.keys[module] = parsedJson[module].keys;
|
||||
json.bundles.main.push(module);
|
||||
}
|
||||
parsedJson = json;
|
||||
break;
|
||||
parsedJson = json;
|
||||
break;
|
||||
}
|
||||
key = 'vscode.' + file.relative.split('/')[0];
|
||||
return { [key]: parsedJson };
|
||||
},
|
||||
}))
|
||||
.pipe(gzip({ append: false }))
|
||||
.pipe(vfs.dest('./nlsMetadata'))
|
||||
.pipe(es.through(function (data) {
|
||||
console.log(`Uploading ${data.path}`);
|
||||
// trigger artifact upload
|
||||
console.log(`##vso[artifact.upload containerfolder=nlsmetadata;artifactname=combined.nls.metadata.json]${data.path}`);
|
||||
this.emit('data', data);
|
||||
}))
|
||||
.pipe(azure.upload({
|
||||
account: process.env.AZURE_STORAGE_ACCOUNT,
|
||||
credential,
|
||||
container: 'nlsmetadata',
|
||||
prefix: commit + '/',
|
||||
contentSettings: {
|
||||
contentEncoding: 'gzip',
|
||||
cacheControl: 'max-age=31536000, public'
|
||||
}
|
||||
key = 'vscode.' + file.relative.split('/')[0];
|
||||
return { [key]: parsedJson };
|
||||
},
|
||||
}))
|
||||
.pipe(gzip({ append: false }))
|
||||
.pipe(vfs.dest('./nlsMetadata'))
|
||||
.pipe(es.through(function (data) {
|
||||
console.log(`Uploading ${data.path}`);
|
||||
// trigger artifact upload
|
||||
console.log(`##vso[artifact.upload containerfolder=nlsmetadata;artifactname=combined.nls.metadata.json]${data.path}`);
|
||||
this.emit('data', data);
|
||||
}))
|
||||
.pipe(azure.upload({
|
||||
account: process.env.AZURE_STORAGE_ACCOUNT,
|
||||
key: process.env.AZURE_STORAGE_ACCESS_KEY,
|
||||
container: 'nlsmetadata',
|
||||
prefix: commit + '/',
|
||||
contentSettings: {
|
||||
contentEncoding: 'gzip',
|
||||
cacheControl: 'max-age=31536000, public'
|
||||
}
|
||||
}));
|
||||
}))
|
||||
.on('end', () => c())
|
||||
.on('error', (err) => e(err));
|
||||
});
|
||||
}
|
||||
main();
|
||||
main().catch(err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
|
@ -12,10 +12,12 @@ import * as vfs from 'vinyl-fs';
|
|||
import * as util from '../lib/util';
|
||||
import * as merge from 'gulp-merge-json';
|
||||
import * as gzip from 'gulp-gzip';
|
||||
import { ClientSecretCredential } from '@azure/identity';
|
||||
const azure = require('gulp-azure-storage');
|
||||
|
||||
const root = path.dirname(path.dirname(__dirname));
|
||||
const commit = util.getVersion(root);
|
||||
const credential = new ClientSecretCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, process.env['AZURE_CLIENT_SECRET']!);
|
||||
|
||||
interface NlsMetadata {
|
||||
keys: { [module: string]: string },
|
||||
|
@ -23,85 +25,94 @@ interface NlsMetadata {
|
|||
bundles: { [bundle: string]: string[] },
|
||||
}
|
||||
|
||||
function main() {
|
||||
return es.merge(
|
||||
vfs.src('out-vscode-web-min/nls.metadata.json', { base: 'out-vscode-web-min' }),
|
||||
vfs.src('.build/extensions/**/nls.metadata.json', { base: '.build/extensions' }),
|
||||
vfs.src('.build/extensions/**/nls.metadata.header.json', { base: '.build/extensions' }),
|
||||
vfs.src('.build/extensions/**/package.nls.json', { base: '.build/extensions' }))
|
||||
.pipe(merge({
|
||||
fileName: 'combined.nls.metadata.json',
|
||||
jsonSpace: '',
|
||||
edit: (parsedJson, file) => {
|
||||
let key;
|
||||
if (file.base === 'out-vscode-web-min') {
|
||||
return { vscode: parsedJson };
|
||||
}
|
||||
function main(): Promise<void> {
|
||||
return new Promise((c, e) => {
|
||||
|
||||
// Handle extensions and follow the same structure as the Core nls file.
|
||||
switch (file.basename) {
|
||||
case 'package.nls.json':
|
||||
// put package.nls.json content in Core NlsMetadata format
|
||||
// language packs use the key "package" to specify that
|
||||
// translations are for the package.json file
|
||||
parsedJson = {
|
||||
messages: {
|
||||
package: Object.values(parsedJson)
|
||||
},
|
||||
keys: {
|
||||
package: Object.keys(parsedJson)
|
||||
},
|
||||
bundles: {
|
||||
main: ['package']
|
||||
es.merge(
|
||||
vfs.src('out-vscode-web-min/nls.metadata.json', { base: 'out-vscode-web-min' }),
|
||||
vfs.src('.build/extensions/**/nls.metadata.json', { base: '.build/extensions' }),
|
||||
vfs.src('.build/extensions/**/nls.metadata.header.json', { base: '.build/extensions' }),
|
||||
vfs.src('.build/extensions/**/package.nls.json', { base: '.build/extensions' }))
|
||||
.pipe(merge({
|
||||
fileName: 'combined.nls.metadata.json',
|
||||
jsonSpace: '',
|
||||
edit: (parsedJson, file) => {
|
||||
let key;
|
||||
if (file.base === 'out-vscode-web-min') {
|
||||
return { vscode: parsedJson };
|
||||
}
|
||||
|
||||
// Handle extensions and follow the same structure as the Core nls file.
|
||||
switch (file.basename) {
|
||||
case 'package.nls.json':
|
||||
// put package.nls.json content in Core NlsMetadata format
|
||||
// language packs use the key "package" to specify that
|
||||
// translations are for the package.json file
|
||||
parsedJson = {
|
||||
messages: {
|
||||
package: Object.values(parsedJson)
|
||||
},
|
||||
keys: {
|
||||
package: Object.keys(parsedJson)
|
||||
},
|
||||
bundles: {
|
||||
main: ['package']
|
||||
}
|
||||
};
|
||||
break;
|
||||
|
||||
case 'nls.metadata.header.json':
|
||||
parsedJson = { header: parsedJson };
|
||||
break;
|
||||
|
||||
case 'nls.metadata.json':
|
||||
// put nls.metadata.json content in Core NlsMetadata format
|
||||
const modules = Object.keys(parsedJson);
|
||||
|
||||
const json: NlsMetadata = {
|
||||
keys: {},
|
||||
messages: {},
|
||||
bundles: {
|
||||
main: []
|
||||
}
|
||||
};
|
||||
for (const module of modules) {
|
||||
json.messages[module] = parsedJson[module].messages;
|
||||
json.keys[module] = parsedJson[module].keys;
|
||||
json.bundles.main.push(module);
|
||||
}
|
||||
};
|
||||
break;
|
||||
|
||||
case 'nls.metadata.header.json':
|
||||
parsedJson = { header: parsedJson };
|
||||
break;
|
||||
|
||||
case 'nls.metadata.json':
|
||||
// put nls.metadata.json content in Core NlsMetadata format
|
||||
const modules = Object.keys(parsedJson);
|
||||
|
||||
const json: NlsMetadata = {
|
||||
keys: {},
|
||||
messages: {},
|
||||
bundles: {
|
||||
main: []
|
||||
}
|
||||
};
|
||||
for (const module of modules) {
|
||||
json.messages[module] = parsedJson[module].messages;
|
||||
json.keys[module] = parsedJson[module].keys;
|
||||
json.bundles.main.push(module);
|
||||
}
|
||||
parsedJson = json;
|
||||
break;
|
||||
parsedJson = json;
|
||||
break;
|
||||
}
|
||||
key = 'vscode.' + file.relative.split('/')[0];
|
||||
return { [key]: parsedJson };
|
||||
},
|
||||
}))
|
||||
.pipe(gzip({ append: false }))
|
||||
.pipe(vfs.dest('./nlsMetadata'))
|
||||
.pipe(es.through(function (data: Vinyl) {
|
||||
console.log(`Uploading ${data.path}`);
|
||||
// trigger artifact upload
|
||||
console.log(`##vso[artifact.upload containerfolder=nlsmetadata;artifactname=combined.nls.metadata.json]${data.path}`);
|
||||
this.emit('data', data);
|
||||
}))
|
||||
.pipe(azure.upload({
|
||||
account: process.env.AZURE_STORAGE_ACCOUNT,
|
||||
credential,
|
||||
container: 'nlsmetadata',
|
||||
prefix: commit + '/',
|
||||
contentSettings: {
|
||||
contentEncoding: 'gzip',
|
||||
cacheControl: 'max-age=31536000, public'
|
||||
}
|
||||
key = 'vscode.' + file.relative.split('/')[0];
|
||||
return { [key]: parsedJson };
|
||||
},
|
||||
}))
|
||||
.pipe(gzip({ append: false }))
|
||||
.pipe(vfs.dest('./nlsMetadata'))
|
||||
.pipe(es.through(function (data: Vinyl) {
|
||||
console.log(`Uploading ${data.path}`);
|
||||
// trigger artifact upload
|
||||
console.log(`##vso[artifact.upload containerfolder=nlsmetadata;artifactname=combined.nls.metadata.json]${data.path}`);
|
||||
this.emit('data', data);
|
||||
}))
|
||||
.pipe(azure.upload({
|
||||
account: process.env.AZURE_STORAGE_ACCOUNT,
|
||||
key: process.env.AZURE_STORAGE_ACCESS_KEY,
|
||||
container: 'nlsmetadata',
|
||||
prefix: commit + '/',
|
||||
contentSettings: {
|
||||
contentEncoding: 'gzip',
|
||||
cacheControl: 'max-age=31536000, public'
|
||||
}
|
||||
}));
|
||||
}))
|
||||
.on('end', () => c())
|
||||
.on('error', (err: any) => e(err));
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
main().catch(err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
|
|
|
@ -10,9 +10,11 @@ const vfs = require("vinyl-fs");
|
|||
const util = require("../lib/util");
|
||||
// @ts-ignore
|
||||
const deps = require("../lib/dependencies");
|
||||
const identity_1 = require("@azure/identity");
|
||||
const azure = require('gulp-azure-storage');
|
||||
const root = path.dirname(path.dirname(__dirname));
|
||||
const commit = util.getVersion(root);
|
||||
const credential = new identity_1.ClientSecretCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], process.env['AZURE_CLIENT_SECRET']);
|
||||
// optionally allow to pass in explicit base/maps to upload
|
||||
const [, , base, maps] = process.argv;
|
||||
function src(base, maps = `${base}/**/*.map`) {
|
||||
|
@ -40,16 +42,23 @@ function main() {
|
|||
else {
|
||||
sources.push(src(base, maps));
|
||||
}
|
||||
return es.merge(...sources)
|
||||
.pipe(es.through(function (data) {
|
||||
console.log('Uploading Sourcemap', data.relative); // debug
|
||||
this.emit('data', data);
|
||||
}))
|
||||
.pipe(azure.upload({
|
||||
account: process.env.AZURE_STORAGE_ACCOUNT,
|
||||
key: process.env.AZURE_STORAGE_ACCESS_KEY,
|
||||
container: 'sourcemaps',
|
||||
prefix: commit + '/'
|
||||
}));
|
||||
return new Promise((c, e) => {
|
||||
es.merge(...sources)
|
||||
.pipe(es.through(function (data) {
|
||||
console.log('Uploading Sourcemap', data.relative); // debug
|
||||
this.emit('data', data);
|
||||
}))
|
||||
.pipe(azure.upload({
|
||||
account: process.env.AZURE_STORAGE_ACCOUNT,
|
||||
credential,
|
||||
container: 'sourcemaps',
|
||||
prefix: commit + '/'
|
||||
}))
|
||||
.on('end', () => c())
|
||||
.on('error', (err) => e(err));
|
||||
});
|
||||
}
|
||||
main();
|
||||
main().catch(err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
|
@ -12,10 +12,12 @@ import * as vfs from 'vinyl-fs';
|
|||
import * as util from '../lib/util';
|
||||
// @ts-ignore
|
||||
import * as deps from '../lib/dependencies';
|
||||
import { ClientSecretCredential } from '@azure/identity';
|
||||
const azure = require('gulp-azure-storage');
|
||||
|
||||
const root = path.dirname(path.dirname(__dirname));
|
||||
const commit = util.getVersion(root);
|
||||
const credential = new ClientSecretCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, process.env['AZURE_CLIENT_SECRET']!);
|
||||
|
||||
// optionally allow to pass in explicit base/maps to upload
|
||||
const [, , base, maps] = process.argv;
|
||||
|
@ -28,8 +30,8 @@ function src(base: string, maps = `${base}/**/*.map`) {
|
|||
}));
|
||||
}
|
||||
|
||||
function main() {
|
||||
const sources = [];
|
||||
function main(): Promise<void> {
|
||||
const sources: any[] = [];
|
||||
|
||||
// vscode client maps (default)
|
||||
if (!base) {
|
||||
|
@ -51,17 +53,25 @@ function main() {
|
|||
sources.push(src(base, maps));
|
||||
}
|
||||
|
||||
return es.merge(...sources)
|
||||
.pipe(es.through(function (data: Vinyl) {
|
||||
console.log('Uploading Sourcemap', data.relative); // debug
|
||||
this.emit('data', data);
|
||||
}))
|
||||
.pipe(azure.upload({
|
||||
account: process.env.AZURE_STORAGE_ACCOUNT,
|
||||
key: process.env.AZURE_STORAGE_ACCESS_KEY,
|
||||
container: 'sourcemaps',
|
||||
prefix: commit + '/'
|
||||
}));
|
||||
return new Promise((c, e) => {
|
||||
es.merge(...sources)
|
||||
.pipe(es.through(function (data: Vinyl) {
|
||||
console.log('Uploading Sourcemap', data.relative); // debug
|
||||
this.emit('data', data);
|
||||
}))
|
||||
.pipe(azure.upload({
|
||||
account: process.env.AZURE_STORAGE_ACCOUNT,
|
||||
credential,
|
||||
container: 'sourcemaps',
|
||||
prefix: commit + '/'
|
||||
}))
|
||||
.on('end', () => c())
|
||||
.on('error', (err: any) => e(err));
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
main().catch(err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ steps:
|
|||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
SecretsFilter: "github-distro-mixin-password,web-storage-account,web-storage-key,ticino-storage-key"
|
||||
SecretsFilter: "github-distro-mixin-password"
|
||||
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
|
@ -99,11 +99,24 @@ steps:
|
|||
yarn gulp vscode-web-min-ci
|
||||
displayName: Build
|
||||
|
||||
- task: AzureCLI@2
|
||||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
scriptType: pscore
|
||||
scriptLocation: inlineScript
|
||||
addSpnToEnvironment: true
|
||||
inlineScript: |
|
||||
Write-Host "##vso[task.setvariable variable=AZURE_TENANT_ID]$env:tenantId"
|
||||
Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_ID]$env:servicePrincipalId"
|
||||
Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_SECRET;issecret=true]$env:servicePrincipalKey"
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
AZURE_STORAGE_ACCOUNT="$(web-storage-account)" \
|
||||
AZURE_STORAGE_ACCESS_KEY="$(web-storage-key)" \
|
||||
node build/azure-pipelines/upload-cdn.js
|
||||
AZURE_STORAGE_ACCOUNT="vscodeweb" \
|
||||
AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \
|
||||
AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \
|
||||
AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \
|
||||
node build/azure-pipelines/upload-cdn
|
||||
displayName: Upload to CDN
|
||||
|
||||
# upload only the workbench.web.api.js source maps because
|
||||
|
@ -111,13 +124,19 @@ steps:
|
|||
# general task to upload source maps has already been run
|
||||
- script: |
|
||||
set -e
|
||||
AZURE_STORAGE_ACCESS_KEY="$(ticino-storage-key)" \
|
||||
AZURE_STORAGE_ACCOUNT="ticino" \
|
||||
AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \
|
||||
AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \
|
||||
AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \
|
||||
node build/azure-pipelines/upload-sourcemaps out-vscode-web-min out-vscode-web-min/vs/workbench/workbench.web.api.js.map
|
||||
displayName: Upload sourcemaps (Web)
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
AZURE_STORAGE_ACCESS_KEY="$(ticino-storage-key)" \
|
||||
AZURE_STORAGE_ACCOUNT="ticino" \
|
||||
AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \
|
||||
AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \
|
||||
AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \
|
||||
node build/azure-pipelines/upload-nlsmetadata
|
||||
displayName: Upload NLS Metadata
|
||||
condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false'))
|
||||
|
|
|
@ -13,7 +13,7 @@ steps:
|
|||
inputs:
|
||||
azureSubscription: "vscode-builds-subscription"
|
||||
KeyVaultName: vscode
|
||||
SecretsFilter: "github-distro-mixin-password,vscode-storage-key,builds-docdb-key-readwrite,ESRP-PKI,esrp-aad-username,esrp-aad-password"
|
||||
SecretsFilter: "github-distro-mixin-password,ESRP-PKI,esrp-aad-username,esrp-aad-password"
|
||||
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
|
@ -187,7 +187,7 @@ steps:
|
|||
$AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json
|
||||
$AppNameShort = $AppProductJson.nameShort
|
||||
exec { $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"; $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)"; .\resources\server\test\test-remote-integration.bat }
|
||||
displayName: Run remote integration tests (Electron)
|
||||
displayName: Run integration tests (Remote)
|
||||
timeoutInMinutes: 7
|
||||
condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64'))
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ const createAsar = require('./lib/asar').createAsar;
|
|||
const minimist = require('minimist');
|
||||
const { compileBuildTask } = require('./gulpfile.compile');
|
||||
const { compileExtensionsBuildTask } = require('./gulpfile.extensions');
|
||||
const { getSettingsSearchBuildId, shouldSetupSettingsSearch } = require('./azure-pipelines/upload-configuration');
|
||||
|
||||
// Build
|
||||
const vscodeEntryPoints = _.flatten([
|
||||
|
@ -475,110 +476,3 @@ gulp.task('vscode-translations-import', function () {
|
|||
.pipe(vfs.dest(`./build/win32/i18n`));
|
||||
}));
|
||||
});
|
||||
|
||||
// This task is only run for the MacOS build
|
||||
const generateVSCodeConfigurationTask = task.define('generate-vscode-configuration', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const buildDir = process.env['AGENT_BUILDDIRECTORY'];
|
||||
if (!buildDir) {
|
||||
return reject(new Error('$AGENT_BUILDDIRECTORY not set'));
|
||||
}
|
||||
|
||||
if (process.env.VSCODE_QUALITY !== 'insider' && process.env.VSCODE_QUALITY !== 'stable') {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
const userDataDir = path.join(os.tmpdir(), 'tmpuserdata');
|
||||
const extensionsDir = path.join(os.tmpdir(), 'tmpextdir');
|
||||
const arch = process.env['VSCODE_ARCH'];
|
||||
const appRoot = path.join(buildDir, `VSCode-darwin-${arch}`);
|
||||
const appName = process.env.VSCODE_QUALITY === 'insider' ? 'Visual\\ Studio\\ Code\\ -\\ Insiders.app' : 'Visual\\ Studio\\ Code.app';
|
||||
const appPath = path.join(appRoot, appName, 'Contents', 'Resources', 'app', 'bin', 'code');
|
||||
const codeProc = cp.exec(
|
||||
`${appPath} --export-default-configuration='${allConfigDetailsPath}' --wait --user-data-dir='${userDataDir}' --extensions-dir='${extensionsDir}'`,
|
||||
(err, stdout, stderr) => {
|
||||
clearTimeout(timer);
|
||||
if (err) {
|
||||
console.log(`err: ${err} ${err.message} ${err.toString()}`);
|
||||
reject(err);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.log(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
const timer = setTimeout(() => {
|
||||
codeProc.kill();
|
||||
reject(new Error('export-default-configuration process timed out'));
|
||||
}, 12 * 1000);
|
||||
|
||||
codeProc.on('error', err => {
|
||||
clearTimeout(timer);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const allConfigDetailsPath = path.join(os.tmpdir(), 'configuration.json');
|
||||
gulp.task(task.define(
|
||||
'upload-vscode-configuration',
|
||||
task.series(
|
||||
generateVSCodeConfigurationTask,
|
||||
() => {
|
||||
const azure = require('gulp-azure-storage');
|
||||
|
||||
if (!shouldSetupSettingsSearch()) {
|
||||
const branch = process.env.BUILD_SOURCEBRANCH;
|
||||
console.log(`Only runs on main and release branches, not ${branch}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fs.existsSync(allConfigDetailsPath)) {
|
||||
throw new Error(`configuration file at ${allConfigDetailsPath} does not exist`);
|
||||
}
|
||||
|
||||
const settingsSearchBuildId = getSettingsSearchBuildId(packageJson);
|
||||
if (!settingsSearchBuildId) {
|
||||
throw new Error('Failed to compute build number');
|
||||
}
|
||||
|
||||
return gulp.src(allConfigDetailsPath)
|
||||
.pipe(azure.upload({
|
||||
account: process.env.AZURE_STORAGE_ACCOUNT,
|
||||
key: process.env.AZURE_STORAGE_ACCESS_KEY,
|
||||
container: 'configuration',
|
||||
prefix: `${settingsSearchBuildId}/${commit}/`
|
||||
}));
|
||||
}
|
||||
)
|
||||
));
|
||||
|
||||
function shouldSetupSettingsSearch() {
|
||||
const branch = process.env.BUILD_SOURCEBRANCH;
|
||||
return branch && (/\/main$/.test(branch) || branch.indexOf('/release/') >= 0);
|
||||
}
|
||||
|
||||
function getSettingsSearchBuildId(packageJson) {
|
||||
try {
|
||||
const branch = process.env.BUILD_SOURCEBRANCH;
|
||||
const branchId = branch.indexOf('/release/') >= 0 ? 0 :
|
||||
/\/main$/.test(branch) ? 1 :
|
||||
2; // Some unexpected branch
|
||||
|
||||
const out = cp.execSync(`git rev-list HEAD --count`);
|
||||
const count = parseInt(out.toString());
|
||||
|
||||
// <version number><commit count><branchId (avoid unlikely conflicts)>
|
||||
// 1.25.1, 1,234,567 commits, main = 1250112345671
|
||||
return util.versionStringToNumber(packageJson.version) * 1e8 + count * 10 + branchId;
|
||||
} catch (e) {
|
||||
throw new Error('Could not determine build number: ' + e.toString());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "monaco-editor-core",
|
||||
"private": true,
|
||||
"version": "0.30.0",
|
||||
"version": "0.31.0",
|
||||
"description": "A browser based code editor",
|
||||
"author": "Microsoft Corporation",
|
||||
"license": "MIT",
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
"fs-extra": "^9.1.0",
|
||||
"got": "11.8.1",
|
||||
"gulp-merge-json": "^2.1.1",
|
||||
"iconv-lite-umd": "0.6.8",
|
||||
"iconv-lite-umd": "0.6.10",
|
||||
"jsonc-parser": "^2.3.0",
|
||||
"mime": "^1.4.1",
|
||||
"mkdirp": "^1.0.4",
|
||||
|
|
|
@ -1627,10 +1627,10 @@ https-proxy-agent@^5.0.0:
|
|||
agent-base "6"
|
||||
debug "4"
|
||||
|
||||
iconv-lite-umd@0.6.8:
|
||||
version "0.6.8"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.8.tgz#5ad310ec126b260621471a2d586f7f37b9958ec0"
|
||||
integrity sha512-zvXJ5gSwMC9JD3wDzH8CoZGc1pbiJn12Tqjk8BXYCnYz3hYL5GRjHW8LEykjXhV9WgNGI4rgpgHcbIiBfrRq6A==
|
||||
iconv-lite-umd@0.6.10:
|
||||
version "0.6.10"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.10.tgz#faec47521e095b8e3a7175ae08e1b4ae0359a735"
|
||||
integrity sha512-8NtgTa/m1jVq7vdywmD5+SqIlZsB59wtsjaylQuExyCojMq1tHVQxmHjeqVSYwKwnmQbH4mZ1Dxx1eqDkPgaqA==
|
||||
|
||||
inflight@^1.0.4:
|
||||
version "1.0.6"
|
||||
|
|
|
@ -81,7 +81,8 @@
|
|||
"command": "git.openChange",
|
||||
"title": "%command.openChange%",
|
||||
"category": "Git",
|
||||
"icon": "$(compare-changes)"
|
||||
"icon": "$(compare-changes)",
|
||||
"enablement": "scmActiveResourceHasChanges"
|
||||
},
|
||||
{
|
||||
"command": "git.openAllChanges",
|
||||
|
@ -2365,7 +2366,7 @@
|
|||
"dependencies": {
|
||||
"byline": "^5.0.0",
|
||||
"file-type": "^7.2.0",
|
||||
"iconv-lite-umd": "0.6.8",
|
||||
"iconv-lite-umd": "0.6.10",
|
||||
"jschardet": "3.0.0",
|
||||
"vscode-extension-telemetry": "0.4.3",
|
||||
"vscode-nls": "^4.0.0",
|
||||
|
|
|
@ -166,8 +166,7 @@ export class GitTimelineProvider implements TimelineProvider {
|
|||
if (showAuthor) {
|
||||
item.description = c.authorName;
|
||||
}
|
||||
// allow-any-unicode-next-line
|
||||
item.detail = `${c.authorName} (${c.authorEmail}) — ${c.hash.substr(0, 8)}\n${dateFormatter.format(date)}\n\n${message}`;
|
||||
item.detail = `${c.authorName} (${c.authorEmail}) \u2014 ${c.hash.substr(0, 8)}\n${dateFormatter.format(date)}\n\n${message}`;
|
||||
|
||||
const cmd = this.commands.resolveTimelineOpenDiffCommand(item, uri);
|
||||
if (cmd) {
|
||||
|
@ -192,8 +191,7 @@ export class GitTimelineProvider implements TimelineProvider {
|
|||
// TODO@eamodio: Replace with a better icon -- reflecting its status maybe?
|
||||
item.iconPath = new ThemeIcon('git-commit');
|
||||
item.description = '';
|
||||
// allow-any-unicode-next-line
|
||||
item.detail = localize('git.timeline.detail', '{0} — {1}\n{2}\n\n{3}', you, localize('git.index', 'Index'), dateFormatter.format(date), Resource.getStatusText(index.type));
|
||||
item.detail = localize('git.timeline.detail', '{0} \u2014 {1}\n{2}\n\n{3}', you, localize('git.index', 'Index'), dateFormatter.format(date), Resource.getStatusText(index.type));
|
||||
|
||||
const cmd = this.commands.resolveTimelineOpenDiffCommand(item, uri);
|
||||
if (cmd) {
|
||||
|
@ -215,8 +213,7 @@ export class GitTimelineProvider implements TimelineProvider {
|
|||
// TODO@eamodio: Replace with a better icon -- reflecting its status maybe?
|
||||
item.iconPath = new ThemeIcon('git-commit');
|
||||
item.description = '';
|
||||
// allow-any-unicode-next-line
|
||||
item.detail = localize('git.timeline.detail', '{0} — {1}\n{2}\n\n{3}', you, localize('git.workingTree', 'Working Tree'), dateFormatter.format(date), Resource.getStatusText(working.type));
|
||||
item.detail = localize('git.timeline.detail', '{0} \u2014 {1}\n{2}\n\n{3}', you, localize('git.workingTree', 'Working Tree'), dateFormatter.format(date), Resource.getStatusText(working.type));
|
||||
|
||||
const cmd = this.commands.resolveTimelineOpenDiffCommand(item, uri);
|
||||
if (cmd) {
|
||||
|
|
|
@ -46,10 +46,10 @@ file-type@^7.2.0:
|
|||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-7.2.0.tgz#113cfed52e1d6959ab80248906e2f25a8cdccb74"
|
||||
integrity sha1-ETz+1S4daVmrgCSJBuLyWozcy3Q=
|
||||
|
||||
iconv-lite-umd@0.6.8:
|
||||
version "0.6.8"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.8.tgz#5ad310ec126b260621471a2d586f7f37b9958ec0"
|
||||
integrity sha512-zvXJ5gSwMC9JD3wDzH8CoZGc1pbiJn12Tqjk8BXYCnYz3hYL5GRjHW8LEykjXhV9WgNGI4rgpgHcbIiBfrRq6A==
|
||||
iconv-lite-umd@0.6.10:
|
||||
version "0.6.10"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.10.tgz#faec47521e095b8e3a7175ae08e1b4ae0359a735"
|
||||
integrity sha512-8NtgTa/m1jVq7vdywmD5+SqIlZsB59wtsjaylQuExyCojMq1tHVQxmHjeqVSYwKwnmQbH4mZ1Dxx1eqDkPgaqA==
|
||||
|
||||
isexe@^2.0.0:
|
||||
version "2.0.0"
|
||||
|
|
|
@ -5,9 +5,8 @@
|
|||
|
||||
import { ExtensionContext, Uri } from 'vscode';
|
||||
import { LanguageClientOptions } from 'vscode-languageclient';
|
||||
import { startClient, LanguageClientConstructor } from '../jsonClient';
|
||||
import { startClient, LanguageClientConstructor, SchemaRequestService } from '../jsonClient';
|
||||
import { LanguageClient } from 'vscode-languageclient/browser';
|
||||
import { RequestService } from '../requests';
|
||||
|
||||
declare const Worker: {
|
||||
new(stringUrl: string): any;
|
||||
|
@ -24,7 +23,7 @@ export function activate(context: ExtensionContext) {
|
|||
return new LanguageClient(id, name, clientOptions, worker);
|
||||
};
|
||||
|
||||
const http: RequestService = {
|
||||
const schemaRequests: SchemaRequestService = {
|
||||
getContent(uri: string) {
|
||||
return fetch(uri, { mode: 'cors' })
|
||||
.then(function (response: any) {
|
||||
|
@ -32,7 +31,8 @@ export function activate(context: ExtensionContext) {
|
|||
});
|
||||
}
|
||||
};
|
||||
startClient(context, newLanguageClient, { http });
|
||||
|
||||
startClient(context, newLanguageClient, { schemaRequests });
|
||||
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
|
|
|
@ -20,7 +20,6 @@ import {
|
|||
} from 'vscode-languageclient';
|
||||
|
||||
import { hash } from './utils/hash';
|
||||
import { RequestService, joinPath } from './requests';
|
||||
import { createLanguageStatusItem } from './languageStatus';
|
||||
|
||||
namespace VSCodeContentRequest {
|
||||
|
@ -96,10 +95,16 @@ export interface TelemetryReporter {
|
|||
export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => CommonLanguageClient;
|
||||
|
||||
export interface Runtime {
|
||||
http: RequestService;
|
||||
schemaRequests: SchemaRequestService;
|
||||
telemetry?: TelemetryReporter
|
||||
}
|
||||
|
||||
export interface SchemaRequestService {
|
||||
getContent(uri: string): Promise<string>;
|
||||
}
|
||||
|
||||
export const languageServerDescription = localize('jsonserver.name', 'JSON Language Server');
|
||||
|
||||
export function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime) {
|
||||
|
||||
const toDispose = context.subscriptions;
|
||||
|
@ -198,7 +203,7 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua
|
|||
};
|
||||
|
||||
// Create the language client and start the client.
|
||||
const client = newLanguageClient('json', localize('jsonserver.name', 'JSON Language Server'), clientOptions);
|
||||
const client = newLanguageClient('json', languageServerDescription, clientOptions);
|
||||
client.registerProposedFeatures();
|
||||
|
||||
const disposable = client.start();
|
||||
|
@ -228,7 +233,7 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua
|
|||
*/
|
||||
runtime.telemetry.sendTelemetryEvent('json.schema', { schemaURL: uriPath });
|
||||
}
|
||||
return runtime.http.getContent(uriPath).catch(e => {
|
||||
return runtime.schemaRequests.getContent(uriPath).catch(e => {
|
||||
return Promise.reject(new ResponseError(4, e.toString()));
|
||||
});
|
||||
} else {
|
||||
|
@ -386,7 +391,7 @@ function getSchemaAssociations(_context: ExtensionContext): ISchemaAssociation[]
|
|||
if (Array.isArray(fileMatch) && typeof url === 'string') {
|
||||
let uri: string = url;
|
||||
if (uri[0] === '.' && uri[1] === '/') {
|
||||
uri = joinPath(extension.extensionUri, uri).toString();
|
||||
uri = Uri.joinPath(extension.extensionUri, uri).toString();
|
||||
}
|
||||
fileMatch = fileMatch.map(fm => {
|
||||
if (fm[0] === '%') {
|
||||
|
@ -507,7 +512,7 @@ function getSchemaId(schema: JSONSchemaSettings, folderUri?: Uri): string | unde
|
|||
url = schema.schema.id || `vscode://schemas/custom/${encodeURIComponent(hash(schema.schema).toString(16))}`;
|
||||
}
|
||||
} else if (folderUri && (url[0] === '.' || url[0] === '/')) {
|
||||
url = joinPath(folderUri, url).toString();
|
||||
url = Uri.joinPath(folderUri, url).toString();
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
|
|
@ -3,28 +3,85 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { window, languages, Uri, LanguageStatusSeverity, Disposable, commands, QuickPickItem } from 'vscode';
|
||||
import { window, languages, Uri, LanguageStatusSeverity, Disposable, commands, QuickPickItem, extensions, workspace } from 'vscode';
|
||||
import { JSONLanguageStatus } from './jsonClient';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
type ShowSchemasInput = {
|
||||
schemas: string[];
|
||||
uri: string;
|
||||
};
|
||||
|
||||
interface ShowSchemasItem extends QuickPickItem {
|
||||
uri: Uri;
|
||||
}
|
||||
|
||||
function equalsIgnoreCase(a: string, b: string): boolean {
|
||||
return a.length === b.length && a.toLowerCase().localeCompare(b.toLowerCase()) === 0;
|
||||
}
|
||||
|
||||
function isEqualAuthority(a1: string | undefined, a2: string | undefined) {
|
||||
return a1 === a2 || (a1 !== undefined && a2 !== undefined && equalsIgnoreCase(a1, a2));
|
||||
}
|
||||
|
||||
function findExtension(uri: Uri) {
|
||||
for (const ext of extensions.all) {
|
||||
const parent = ext.extensionUri;
|
||||
if (uri.scheme === parent.scheme && isEqualAuthority(uri.authority, parent.authority) && uri.path.startsWith(parent.path + '/')) {
|
||||
return ext;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function findWorkspaceFolder(uri: Uri) {
|
||||
if (workspace.workspaceFolders) {
|
||||
for (const wf of workspace.workspaceFolders) {
|
||||
const parent = wf.uri;
|
||||
if (uri.scheme === parent.scheme && isEqualAuthority(uri.authority, parent.authority) && uri.path.startsWith(parent.path + '/')) {
|
||||
return wf;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function renderShowSchemasItem(schema: string): ShowSchemasItem {
|
||||
const uri = Uri.parse(schema);
|
||||
const extension = findExtension(uri);
|
||||
if (extension) {
|
||||
return { label: extension.id, description: uri.path.substring(extension.extensionUri.path.length + 1), uri };
|
||||
}
|
||||
const wf = findWorkspaceFolder(uri);
|
||||
if (wf) {
|
||||
return { label: uri.path.substring(wf.uri.path.length + 1), description: 'Workspace', uri };
|
||||
}
|
||||
if (uri.scheme === 'file') {
|
||||
return { label: uri.fsPath, uri };
|
||||
} else if (uri.scheme === 'vscode') {
|
||||
return { label: schema, description: 'internally generated', uri };
|
||||
}
|
||||
return { label: schema, uri };
|
||||
}
|
||||
|
||||
|
||||
export function createLanguageStatusItem(documentSelector: string[], statusRequest: (uri: string) => Promise<JSONLanguageStatus>): Disposable {
|
||||
const statusItem = languages.createLanguageStatusItem('json.projectStatus', documentSelector);
|
||||
statusItem.name = localize('statusItem.name', "JSON Validation Status");
|
||||
statusItem.severity = LanguageStatusSeverity.Information;
|
||||
|
||||
const showSchemasCommand = commands.registerCommand('_json.showAssociatedSchemaList', arg => {
|
||||
const items = arg.schemas.sort().map((a: string) => ({ label: a }));
|
||||
const quickPick = window.createQuickPick<QuickPickItem>();
|
||||
quickPick.title = localize('schemaPicker.title', 'Associated JSON Schemas');
|
||||
const showSchemasCommand = commands.registerCommand('_json.showAssociatedSchemaList', (arg: ShowSchemasInput) => {
|
||||
const items: ShowSchemasItem[] = arg.schemas.sort().map(renderShowSchemasItem);
|
||||
const quickPick = window.createQuickPick<ShowSchemasItem>();
|
||||
quickPick.title = localize('schemaPicker.title', 'JSON Schemas used for {0}', arg.uri.toString());
|
||||
quickPick.placeholder = localize('schemaPicker.placeholder', 'Select the schema to open');
|
||||
quickPick.items = items;
|
||||
quickPick.show();
|
||||
quickPick.onDidAccept(() => {
|
||||
const selectedSchema = quickPick.selectedItems[0].label;
|
||||
commands.executeCommand('vscode.open', Uri.parse(selectedSchema));
|
||||
commands.executeCommand('vscode.open', quickPick.selectedItems[0].uri);
|
||||
quickPick.dispose();
|
||||
});
|
||||
});
|
||||
|
@ -46,19 +103,20 @@ export function createLanguageStatusItem(documentSelector: string[], statusReque
|
|||
if (schemas.length === 0) {
|
||||
statusItem.text = localize('status.noSchema', 'Validated without JSON schema');
|
||||
} else if (schemas.length === 1) {
|
||||
const item = renderShowSchemasItem(schemas[0]);
|
||||
statusItem.text = localize('status.singleSchema', 'Validated with JSON schema');
|
||||
statusItem.command = {
|
||||
command: 'vscode.open',
|
||||
title: localize('status.openSchemaLink', 'Open Schema'),
|
||||
tooltip: schemas[0],
|
||||
arguments: [Uri.parse(schemas[0])]
|
||||
tooltip: item.description ? `${item.label} - ${item.description}` : item.label,
|
||||
arguments: [item.uri]
|
||||
};
|
||||
} else {
|
||||
statusItem.text = localize('status.multipleSchema', 'Validated with multiple JSON schemas');
|
||||
statusItem.command = {
|
||||
command: '_json.showAssociatedSchemaList',
|
||||
title: localize('status.openSchemasLink', 'Show Schemas'),
|
||||
arguments: [{ schemas }]
|
||||
arguments: [{ schemas, uri: document.uri.toString() } as ShowSchemasInput]
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -79,5 +137,3 @@ export function createLanguageStatusItem(documentSelector: string[], statusReque
|
|||
return Disposable.from(statusItem, activeEditorListener, showSchemasCommand);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -3,24 +3,26 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ExtensionContext } from 'vscode';
|
||||
import { startClient, LanguageClientConstructor } from '../jsonClient';
|
||||
import { ExtensionContext, OutputChannel, window, workspace } from 'vscode';
|
||||
import { startClient, LanguageClientConstructor, SchemaRequestService, languageServerDescription } from '../jsonClient';
|
||||
import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient } from 'vscode-languageclient/node';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import { xhr, XHRResponse, getErrorStatusDescription } from 'request-light';
|
||||
import { promises as fs } from 'fs';
|
||||
import * as path from 'path';
|
||||
import { xhr, XHRResponse, getErrorStatusDescription, Headers } from 'request-light';
|
||||
|
||||
import TelemetryReporter from 'vscode-extension-telemetry';
|
||||
import { RequestService } from '../requests';
|
||||
import { JSONSchemaCache } from './schemaCache';
|
||||
|
||||
let telemetry: TelemetryReporter | undefined;
|
||||
|
||||
// this method is called when vs code is activated
|
||||
export function activate(context: ExtensionContext) {
|
||||
|
||||
const clientPackageJSON = getPackageInfo(context);
|
||||
export async function activate(context: ExtensionContext) {
|
||||
const clientPackageJSON = await getPackageInfo(context);
|
||||
telemetry = new TelemetryReporter(clientPackageJSON.name, clientPackageJSON.version, clientPackageJSON.aiKey);
|
||||
|
||||
const outputChannel = window.createOutputChannel(languageServerDescription);
|
||||
|
||||
const serverMain = `./server/${clientPackageJSON.main.indexOf('/dist/') !== -1 ? 'dist' : 'out'}/node/jsonServerMain`;
|
||||
const serverModule = context.asAbsolutePath(serverMain);
|
||||
|
||||
|
@ -35,10 +37,15 @@ export function activate(context: ExtensionContext) {
|
|||
};
|
||||
|
||||
const newLanguageClient: LanguageClientConstructor = (id: string, name: string, clientOptions: LanguageClientOptions) => {
|
||||
clientOptions.outputChannel = outputChannel;
|
||||
return new LanguageClient(id, name, serverOptions, clientOptions);
|
||||
};
|
||||
const log = getLog(outputChannel);
|
||||
context.subscriptions.push(log);
|
||||
|
||||
startClient(context, newLanguageClient, { http: getHTTPRequestService(), telemetry });
|
||||
const schemaRequests = await getSchemaRequestService(context, log);
|
||||
|
||||
startClient(context, newLanguageClient, { schemaRequests, telemetry });
|
||||
}
|
||||
|
||||
export function deactivate(): Promise<any> {
|
||||
|
@ -52,23 +59,88 @@ interface IPackageInfo {
|
|||
main: string;
|
||||
}
|
||||
|
||||
function getPackageInfo(context: ExtensionContext): IPackageInfo {
|
||||
async function getPackageInfo(context: ExtensionContext): Promise<IPackageInfo> {
|
||||
const location = context.asAbsolutePath('./package.json');
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(location).toString());
|
||||
return JSON.parse((await fs.readFile(location)).toString());
|
||||
} catch (e) {
|
||||
console.log(`Problems reading ${location}: ${e}`);
|
||||
return { name: '', version: '', aiKey: '', main: '' };
|
||||
}
|
||||
}
|
||||
|
||||
function getHTTPRequestService(): RequestService {
|
||||
interface Log {
|
||||
trace(message: string): void;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
const traceSetting = 'json.trace.server';
|
||||
function getLog(outputChannel: OutputChannel): Log {
|
||||
let trace = workspace.getConfiguration().get(traceSetting) === 'verbose';
|
||||
const configListener = workspace.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(traceSetting)) {
|
||||
trace = workspace.getConfiguration().get(traceSetting) === 'verbose';
|
||||
}
|
||||
});
|
||||
return {
|
||||
getContent(uri: string, _encoding?: string): Promise<string> {
|
||||
const headers = { 'Accept-Encoding': 'gzip, deflate' };
|
||||
return xhr({ url: uri, followRedirects: 5, headers }).then(response => {
|
||||
return response.responseText;
|
||||
}, (error: XHRResponse) => {
|
||||
trace(message: string) {
|
||||
if (trace) {
|
||||
outputChannel.appendLine(message);
|
||||
}
|
||||
},
|
||||
dispose: () => configListener.dispose()
|
||||
};
|
||||
}
|
||||
|
||||
const retryTimeoutInDays = 2; // 2 days
|
||||
const retryTimeoutInMs = retryTimeoutInDays * 24 * 60 * 60 * 1000;
|
||||
|
||||
async function getSchemaRequestService(context: ExtensionContext, log: Log): Promise<SchemaRequestService> {
|
||||
let cache: JSONSchemaCache | undefined = undefined;
|
||||
const globalStorage = context.globalStorageUri;
|
||||
if (globalStorage.scheme === 'file') {
|
||||
const schemaCacheLocation = path.join(globalStorage.fsPath, 'json-schema-cache');
|
||||
await fs.mkdir(schemaCacheLocation, { recursive: true });
|
||||
|
||||
cache = new JSONSchemaCache(schemaCacheLocation, context.globalState);
|
||||
log.trace(`[json schema cache] initial state: ${JSON.stringify(cache.getCacheInfo(), null, ' ')}`);
|
||||
}
|
||||
|
||||
const isXHRResponse = (error: any): error is XHRResponse => typeof error?.status === 'number';
|
||||
|
||||
const request = async (uri: string, etag?: string): Promise<string> => {
|
||||
const headers: Headers = { 'Accept-Encoding': 'gzip, deflate' };
|
||||
if (etag) {
|
||||
headers['If-None-Match'] = etag;
|
||||
}
|
||||
try {
|
||||
log.trace(`[json schema cache] Requesting schema ${uri} etag ${etag}...`);
|
||||
|
||||
const response = await xhr({ url: uri, followRedirects: 5, headers });
|
||||
if (cache) {
|
||||
const etag = response.headers['etag'];
|
||||
if (typeof etag === 'string') {
|
||||
log.trace(`[json schema cache] Storing schema ${uri} etag ${etag} in cache`);
|
||||
await cache.putSchema(uri, etag, response.responseText);
|
||||
} else {
|
||||
log.trace(`[json schema cache] Response: schema ${uri} no etag`);
|
||||
}
|
||||
}
|
||||
return response.responseText;
|
||||
} catch (error: unknown) {
|
||||
if (isXHRResponse(error)) {
|
||||
if (error.status === 304 && etag && cache) {
|
||||
|
||||
log.trace(`[json schema cache] Response: schema ${uri} unchanged etag ${etag}`);
|
||||
|
||||
const content = await cache.getSchema(uri, etag);
|
||||
if (content) {
|
||||
log.trace(`[json schema cache] Get schema ${uri} etag ${etag} from cache`);
|
||||
return content;
|
||||
}
|
||||
return request(uri);
|
||||
}
|
||||
|
||||
let status = getErrorStatusDescription(error.status);
|
||||
if (status && error.responseText) {
|
||||
status = `${status}\n${error.responseText.substring(0, 200)}`;
|
||||
|
@ -76,8 +148,24 @@ function getHTTPRequestService(): RequestService {
|
|||
if (!status) {
|
||||
status = error.toString();
|
||||
}
|
||||
return Promise.reject(status);
|
||||
});
|
||||
log.trace(`[json schema cache] Respond schema ${uri} error ${status}`);
|
||||
|
||||
throw status;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
getContent: async (uri: string) => {
|
||||
if (cache && /^https?:\/\/json\.schemastore\.org\//.test(uri)) {
|
||||
const content = await cache.getSchemaIfAccessedSince(uri, retryTimeoutInMs);
|
||||
if (content) {
|
||||
log.trace(`[json schema cache] Schema ${uri} from cache without request (last accessed less than ${retryTimeoutInDays} days ago)`);
|
||||
return content;
|
||||
}
|
||||
}
|
||||
return request(uri, cache?.getETag(uri));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
108
extensions/json-language-features/client/src/node/schemaCache.ts
Normal file
108
extensions/json-language-features/client/src/node/schemaCache.ts
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { promises as fs } from 'fs';
|
||||
import * as path from 'path';
|
||||
import { createHash } from 'crypto';
|
||||
import { Memento } from 'vscode';
|
||||
|
||||
interface CacheEntry {
|
||||
etag: string;
|
||||
fileName: string;
|
||||
accessTime: number;
|
||||
}
|
||||
|
||||
interface CacheInfo {
|
||||
[schemaUri: string]: CacheEntry;
|
||||
}
|
||||
|
||||
const MEMENTO_KEY = 'json-schema-cache';
|
||||
|
||||
export class JSONSchemaCache {
|
||||
private readonly cacheInfo: CacheInfo;
|
||||
|
||||
constructor(private readonly schemaCacheLocation: string, private readonly globalState: Memento) {
|
||||
this.cacheInfo = globalState.get<CacheInfo>(MEMENTO_KEY, {});
|
||||
}
|
||||
|
||||
getETag(schemaUri: string): string | undefined {
|
||||
return this.cacheInfo[schemaUri]?.etag;
|
||||
}
|
||||
|
||||
async putSchema(schemaUri: string, etag: string, schemaContent: string): Promise<void> {
|
||||
try {
|
||||
const fileName = getCacheFileName(schemaUri);
|
||||
await fs.writeFile(path.join(this.schemaCacheLocation, fileName), schemaContent);
|
||||
const entry: CacheEntry = { etag, fileName, accessTime: new Date().getTime() };
|
||||
this.cacheInfo[schemaUri] = entry;
|
||||
} catch (e) {
|
||||
delete this.cacheInfo[schemaUri];
|
||||
} finally {
|
||||
await this.updateMemento();
|
||||
}
|
||||
}
|
||||
|
||||
async getSchemaIfAccessedSince(schemaUri: string, expirationDuration: number): Promise<string | undefined> {
|
||||
const cacheEntry = this.cacheInfo[schemaUri];
|
||||
if (cacheEntry && cacheEntry.accessTime + expirationDuration >= new Date().getTime()) {
|
||||
return this.loadSchemaFile(schemaUri, cacheEntry);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async getSchema(schemaUri: string, etag: string): Promise<string | undefined> {
|
||||
const cacheEntry = this.cacheInfo[schemaUri];
|
||||
if (cacheEntry) {
|
||||
if (cacheEntry.etag === etag) {
|
||||
return this.loadSchemaFile(schemaUri, cacheEntry);
|
||||
} else {
|
||||
this.deleteSchemaFile(schemaUri, cacheEntry);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async loadSchemaFile(schemaUri: string, cacheEntry: CacheEntry): Promise<string | undefined> {
|
||||
const cacheLocation = path.join(this.schemaCacheLocation, cacheEntry.fileName);
|
||||
try {
|
||||
const content = (await fs.readFile(cacheLocation)).toString();
|
||||
cacheEntry.accessTime = new Date().getTime();
|
||||
return content;
|
||||
} catch (e) {
|
||||
delete this.cacheInfo[schemaUri];
|
||||
return undefined;
|
||||
} finally {
|
||||
await this.updateMemento();
|
||||
}
|
||||
}
|
||||
|
||||
private async deleteSchemaFile(schemaUri: string, cacheEntry: CacheEntry): Promise<void> {
|
||||
const cacheLocation = path.join(this.schemaCacheLocation, cacheEntry.fileName);
|
||||
delete this.cacheInfo[schemaUri];
|
||||
await this.updateMemento();
|
||||
try {
|
||||
await fs.rm(cacheLocation);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// for debugging
|
||||
public getCacheInfo() {
|
||||
return this.cacheInfo;
|
||||
}
|
||||
|
||||
private async updateMemento() {
|
||||
try {
|
||||
await this.globalState.update(MEMENTO_KEY, this.cacheInfo);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
function getCacheFileName(uri: string): string {
|
||||
return `${createHash('MD5').update(uri).digest('hex')}.schema.json`;
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Uri } from 'vscode';
|
||||
|
||||
export interface RequestService {
|
||||
getContent(uri: string, encoding?: string): Promise<string>;
|
||||
}
|
||||
|
||||
export function getScheme(uri: string) {
|
||||
return uri.substr(0, uri.indexOf(':'));
|
||||
}
|
||||
|
||||
export function dirname(uri: string) {
|
||||
const lastIndexOfSlash = uri.lastIndexOf('/');
|
||||
return lastIndexOfSlash !== -1 ? uri.substr(0, lastIndexOfSlash) : '';
|
||||
}
|
||||
|
||||
export function basename(uri: string) {
|
||||
const lastIndexOfSlash = uri.lastIndexOf('/');
|
||||
return uri.substr(lastIndexOfSlash + 1);
|
||||
}
|
||||
|
||||
const Slash = '/'.charCodeAt(0);
|
||||
const Dot = '.'.charCodeAt(0);
|
||||
|
||||
export function isAbsolutePath(path: string) {
|
||||
return path.charCodeAt(0) === Slash;
|
||||
}
|
||||
|
||||
export function resolvePath(uri: Uri, path: string): Uri {
|
||||
if (isAbsolutePath(path)) {
|
||||
return uri.with({ path: normalizePath(path.split('/')) });
|
||||
}
|
||||
return joinPath(uri, path);
|
||||
}
|
||||
|
||||
export function normalizePath(parts: string[]): string {
|
||||
const newParts: string[] = [];
|
||||
for (const part of parts) {
|
||||
if (part.length === 0 || part.length === 1 && part.charCodeAt(0) === Dot) {
|
||||
// ignore
|
||||
} else if (part.length === 2 && part.charCodeAt(0) === Dot && part.charCodeAt(1) === Dot) {
|
||||
newParts.pop();
|
||||
} else {
|
||||
newParts.push(part);
|
||||
}
|
||||
}
|
||||
if (parts.length > 1 && parts[parts.length - 1].length === 0) {
|
||||
newParts.push('');
|
||||
}
|
||||
let res = newParts.join('/');
|
||||
if (parts[0].length === 0) {
|
||||
res = '/' + res;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
export function joinPath(uri: Uri, ...paths: string[]): Uri {
|
||||
const parts = uri.path.split('/');
|
||||
for (let path of paths) {
|
||||
parts.push(...path.split('/'));
|
||||
}
|
||||
return uri.with({ path: normalizePath(parts) });
|
||||
}
|
|
@ -12,7 +12,7 @@ import {
|
|||
import { formatError, runSafe, runSafeAsync } from './utils/runner';
|
||||
import { TextDocument, JSONDocument, JSONSchema, getLanguageService, DocumentLanguageSettings, SchemaConfiguration, ClientCapabilities, Diagnostic, Range, Position } from 'vscode-json-languageservice';
|
||||
import { getLanguageModelCache } from './languageModelCache';
|
||||
import { RequestService, basename, resolvePath } from './requests';
|
||||
import { Utils, URI } from 'vscode-uri';
|
||||
|
||||
type ISchemaAssociations = Record<string, string[]>;
|
||||
|
||||
|
@ -45,11 +45,15 @@ namespace LanguageStatusRequest {
|
|||
|
||||
const workspaceContext = {
|
||||
resolveRelativePath: (relativePath: string, resource: string) => {
|
||||
const base = resource.substr(0, resource.lastIndexOf('/') + 1);
|
||||
return resolvePath(base, relativePath);
|
||||
const base = resource.substring(0, resource.lastIndexOf('/') + 1);
|
||||
return Utils.resolvePath(URI.parse(base), relativePath).toString();
|
||||
}
|
||||
};
|
||||
|
||||
export interface RequestService {
|
||||
getContent(uri: string): Promise<string>;
|
||||
}
|
||||
|
||||
export interface RuntimeEnvironment {
|
||||
file?: RequestService;
|
||||
http?: RequestService
|
||||
|
@ -185,7 +189,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
|
|||
|
||||
const showLimitedNotification = (uri: string, resultLimit: number) => {
|
||||
const warning = pendingWarnings[uri];
|
||||
connection.sendNotification(ResultLimitReachedNotification.type, `${basename(uri)}: For performance reasons, ${Object.keys(warning.features).join(' and ')} have been limited to ${resultLimit} items.`);
|
||||
connection.sendNotification(ResultLimitReachedNotification.type, `${Utils.basename(URI.parse(uri))}: For performance reasons, ${Object.keys(warning.features).join(' and ')} have been limited to ${resultLimit} items.`);
|
||||
warning.timeout = undefined;
|
||||
};
|
||||
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
|
||||
import { createConnection, Connection, Disposable } from 'vscode-languageserver/node';
|
||||
import { formatError } from '../utils/runner';
|
||||
import { RuntimeEnvironment, startServer } from '../jsonServer';
|
||||
import { RequestService } from '../requests';
|
||||
import { RequestService, RuntimeEnvironment, startServer } from '../jsonServer';
|
||||
|
||||
import { xhr, XHRResponse, configure as configureHttpRequests, getErrorStatusDescription } from 'request-light';
|
||||
import { URI as Uri } from 'vscode-uri';
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI } from 'vscode-uri';
|
||||
|
||||
export interface RequestService {
|
||||
getContent(uri: string, encoding?: string): Promise<string>;
|
||||
}
|
||||
|
||||
export function getScheme(uri: string) {
|
||||
return uri.substr(0, uri.indexOf(':'));
|
||||
}
|
||||
|
||||
export function dirname(uri: string) {
|
||||
const lastIndexOfSlash = uri.lastIndexOf('/');
|
||||
return lastIndexOfSlash !== -1 ? uri.substr(0, lastIndexOfSlash) : '';
|
||||
}
|
||||
|
||||
export function basename(uri: string) {
|
||||
const lastIndexOfSlash = uri.lastIndexOf('/');
|
||||
return uri.substr(lastIndexOfSlash + 1);
|
||||
}
|
||||
|
||||
|
||||
const Slash = '/'.charCodeAt(0);
|
||||
const Dot = '.'.charCodeAt(0);
|
||||
|
||||
export function extname(uri: string) {
|
||||
for (let i = uri.length - 1; i >= 0; i--) {
|
||||
const ch = uri.charCodeAt(i);
|
||||
if (ch === Dot) {
|
||||
if (i > 0 && uri.charCodeAt(i - 1) !== Slash) {
|
||||
return uri.substr(i);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else if (ch === Slash) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
export function isAbsolutePath(path: string) {
|
||||
return path.charCodeAt(0) === Slash;
|
||||
}
|
||||
|
||||
export function resolvePath(uriString: string, path: string): string {
|
||||
if (isAbsolutePath(path)) {
|
||||
const uri = URI.parse(uriString);
|
||||
const parts = path.split('/');
|
||||
return uri.with({ path: normalizePath(parts) }).toString();
|
||||
}
|
||||
return joinPath(uriString, path);
|
||||
}
|
||||
|
||||
export function normalizePath(parts: string[]): string {
|
||||
const newParts: string[] = [];
|
||||
for (const part of parts) {
|
||||
if (part.length === 0 || part.length === 1 && part.charCodeAt(0) === Dot) {
|
||||
// ignore
|
||||
} else if (part.length === 2 && part.charCodeAt(0) === Dot && part.charCodeAt(1) === Dot) {
|
||||
newParts.pop();
|
||||
} else {
|
||||
newParts.push(part);
|
||||
}
|
||||
}
|
||||
if (parts.length > 1 && parts[parts.length - 1].length === 0) {
|
||||
newParts.push('');
|
||||
}
|
||||
let res = newParts.join('/');
|
||||
if (parts[0].length === 0) {
|
||||
res = '/' + res;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
export function joinPath(uriString: string, ...paths: string[]): string {
|
||||
const uri = URI.parse(uriString);
|
||||
const parts = uri.path.split('/');
|
||||
for (let path of paths) {
|
||||
parts.push(...path.split('/'));
|
||||
}
|
||||
return uri.with({ path: normalizePath(parts) }).toString();
|
||||
}
|
|
@ -142,20 +142,16 @@ export class AzureActiveDirectoryService {
|
|||
} catch (e) {
|
||||
// If we aren't connected to the internet, then wait and try to refresh again later.
|
||||
if (e.message === REFRESH_NETWORK_FAILURE) {
|
||||
const didSucceedOnRetry = await this.handleRefreshNetworkError(session.id, session.refreshToken, session.scope);
|
||||
if (!didSucceedOnRetry) {
|
||||
this._tokens.push({
|
||||
accessToken: undefined,
|
||||
refreshToken: session.refreshToken,
|
||||
account: {
|
||||
label: session.account.label ?? session.account.displayName!,
|
||||
id: session.account.id
|
||||
},
|
||||
scope: session.scope,
|
||||
sessionId: session.id
|
||||
});
|
||||
this.pollForReconnect(session.id, session.refreshToken, session.scope);
|
||||
}
|
||||
this._tokens.push({
|
||||
accessToken: undefined,
|
||||
refreshToken: session.refreshToken,
|
||||
account: {
|
||||
label: session.account.label ?? session.account.displayName!,
|
||||
id: session.account.id
|
||||
},
|
||||
scope: session.scope,
|
||||
sessionId: session.id
|
||||
});
|
||||
} else {
|
||||
await this.removeSession(session.id);
|
||||
}
|
||||
|
@ -201,9 +197,8 @@ export class AzureActiveDirectoryService {
|
|||
const token = await this.refreshToken(session.refreshToken, session.scope, session.id);
|
||||
added.push(this.convertToSessionSync(token));
|
||||
} catch (e) {
|
||||
if (e.message === REFRESH_NETWORK_FAILURE) {
|
||||
// Ignore, will automatically retry on next poll.
|
||||
} else {
|
||||
// Network failures will automatically retry on next poll.
|
||||
if (e.message !== REFRESH_NETWORK_FAILURE) {
|
||||
await this.removeSession(session.id);
|
||||
}
|
||||
}
|
||||
|
@ -323,7 +318,7 @@ export class AzureActiveDirectoryService {
|
|||
}
|
||||
}
|
||||
if (!scopes) {
|
||||
const sessions = await this.sessions;
|
||||
const sessions = this._tokens.map(token => this.convertToSessionSync(token));
|
||||
Logger.info(`Got ${sessions.length} sessions for all scopes...`);
|
||||
return sessions;
|
||||
}
|
||||
|
@ -529,12 +524,7 @@ export class AzureActiveDirectoryService {
|
|||
Logger.info('Triggering change session event...');
|
||||
onDidChangeSessions.fire({ added: [], removed: [], changed: [this.convertToSessionSync(refreshedToken)] });
|
||||
} catch (e) {
|
||||
if (e.message === REFRESH_NETWORK_FAILURE) {
|
||||
const didSucceedOnRetry = await this.handleRefreshNetworkError(token.sessionId, token.refreshToken, scope);
|
||||
if (!didSucceedOnRetry) {
|
||||
this.pollForReconnect(token.sessionId, token.refreshToken, token.scope);
|
||||
}
|
||||
} else {
|
||||
if (e.message !== REFRESH_NETWORK_FAILURE) {
|
||||
await this.removeSession(token.sessionId);
|
||||
onDidChangeSessions.fire({ added: [], removed: [this.convertToSessionSync(token)], changed: [] });
|
||||
}
|
||||
|
@ -591,23 +581,9 @@ export class AzureActiveDirectoryService {
|
|||
const endpointUrl = proxyEndpoints?.microsoft || loginEndpointUrl;
|
||||
const endpoint = `${endpointUrl}${tenant}/oauth2/v2.0/token`;
|
||||
|
||||
const result = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Content-Length': postData.length.toString()
|
||||
},
|
||||
body: postData
|
||||
});
|
||||
|
||||
if (result.ok) {
|
||||
const json = await result.json();
|
||||
Logger.info(`Exchanging login code for token (for scopes: ${scope}) succeeded!`);
|
||||
return this.getTokenFromResponse(json, scope);
|
||||
} else {
|
||||
Logger.error(`Exchanging login code for token (for scopes: ${scope}) failed: ${await result.text()}`);
|
||||
throw new Error('Unable to login.');
|
||||
}
|
||||
const json = await this.fetchTokenResponse(endpoint, postData, scope);
|
||||
Logger.info(`Exchanging login code for token (for scopes: ${scope}) succeeded!`);
|
||||
return this.getTokenFromResponse(json, scope);
|
||||
} catch (e) {
|
||||
Logger.error(`Error exchanging code for token (for scopes ${scope}): ${e}`);
|
||||
throw e;
|
||||
|
@ -624,6 +600,46 @@ export class AzureActiveDirectoryService {
|
|||
}
|
||||
}
|
||||
|
||||
private async fetchTokenResponse(endpoint: string, postData: string, scopes: string): Promise<ITokenResponse> {
|
||||
let attempts = 0;
|
||||
while (attempts <= 3) {
|
||||
attempts++;
|
||||
let result: Response | undefined;
|
||||
let errorMessage: string | undefined;
|
||||
try {
|
||||
result = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Content-Length': postData.length.toString()
|
||||
},
|
||||
body: postData
|
||||
});
|
||||
} catch (e) {
|
||||
errorMessage = e.message ?? e;
|
||||
}
|
||||
|
||||
if (!result || result.status > 499) {
|
||||
if (attempts > 3) {
|
||||
Logger.error(`Fetching token failed for scopes (${scopes}): ${result ? await result.text() : errorMessage}`);
|
||||
break;
|
||||
}
|
||||
// Exponential backoff
|
||||
await new Promise(resolve => setTimeout(resolve, 5 * attempts * attempts * 1000));
|
||||
continue;
|
||||
} else if (!result.ok) {
|
||||
// For 4XX errors, the user may actually have an expired token or have changed
|
||||
// their password recently which is throwing a 4XX. For this, we throw an error
|
||||
// so that the user can be prompted to sign in again.
|
||||
throw new Error(await result.text());
|
||||
}
|
||||
|
||||
return await result.json() as ITokenResponse;
|
||||
}
|
||||
|
||||
throw new Error(REFRESH_NETWORK_FAILURE);
|
||||
}
|
||||
|
||||
private async doRefreshToken(refreshToken: string, scope: string, sessionId: string): Promise<IToken> {
|
||||
Logger.info(`Refreshing token for scopes: ${scope}`);
|
||||
const postData = querystring.stringify({
|
||||
|
@ -633,37 +649,25 @@ export class AzureActiveDirectoryService {
|
|||
scope: scope
|
||||
});
|
||||
|
||||
let result: Response;
|
||||
try {
|
||||
const proxyEndpoints: { [providerId: string]: string } | undefined = await vscode.commands.executeCommand('workbench.getCodeExchangeProxyEndpoints');
|
||||
const endpointUrl = proxyEndpoints?.microsoft || loginEndpointUrl;
|
||||
const endpoint = `${endpointUrl}${tenant}/oauth2/v2.0/token`;
|
||||
result = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Content-Length': postData.length.toString()
|
||||
},
|
||||
body: postData
|
||||
});
|
||||
} catch (e) {
|
||||
Logger.error(`Refreshing token failed (for scopes: ${scope}) Error: ${e}`);
|
||||
throw new Error(REFRESH_NETWORK_FAILURE);
|
||||
}
|
||||
const proxyEndpoints: { [providerId: string]: string } | undefined = await vscode.commands.executeCommand('workbench.getCodeExchangeProxyEndpoints');
|
||||
const endpointUrl = proxyEndpoints?.microsoft || loginEndpointUrl;
|
||||
const endpoint = `${endpointUrl}${tenant}/oauth2/v2.0/token`;
|
||||
|
||||
try {
|
||||
if (result.ok) {
|
||||
const json = await result.json();
|
||||
const token = this.getTokenFromResponse(json, scope, sessionId);
|
||||
await this.setToken(token, scope);
|
||||
Logger.info(`Token refresh success for scopes: ${token.scope}`);
|
||||
return token;
|
||||
} else {
|
||||
throw new Error('Bad request.');
|
||||
}
|
||||
const json = await this.fetchTokenResponse(endpoint, postData, scope);
|
||||
const token = this.getTokenFromResponse(json, scope, sessionId);
|
||||
await this.setToken(token, scope);
|
||||
Logger.info(`Token refresh success for scopes: ${token.scope}`);
|
||||
return token;
|
||||
} catch (e) {
|
||||
if (e.message === REFRESH_NETWORK_FAILURE) {
|
||||
// We were unable to refresh because of a network failure (i.e. the user lost internet access).
|
||||
// so set up a timeout to try again later.
|
||||
this.pollForReconnect(sessionId, refreshToken, scope);
|
||||
throw e;
|
||||
}
|
||||
vscode.window.showErrorMessage(localize('signOut', "You have been signed out because reading stored authentication information failed."));
|
||||
Logger.error(`Refreshing token failed (for scopes: ${scope}): ${result.statusText}`);
|
||||
Logger.error(`Refreshing token failed (for scopes: ${scope}): ${e.message}`);
|
||||
throw new Error('Refreshing token failed');
|
||||
}
|
||||
}
|
||||
|
@ -690,7 +694,7 @@ export class AzureActiveDirectoryService {
|
|||
|
||||
private pollForReconnect(sessionId: string, refreshToken: string, scope: string): void {
|
||||
this.clearSessionTimeout(sessionId);
|
||||
|
||||
Logger.trace(`Setting up reconnection timeout for scopes: ${scope}...`);
|
||||
this._refreshTimeouts.set(sessionId, setTimeout(async () => {
|
||||
try {
|
||||
const refreshedToken = await this.refreshToken(refreshToken, scope, sessionId);
|
||||
|
@ -701,29 +705,6 @@ export class AzureActiveDirectoryService {
|
|||
}, 1000 * 60 * 30));
|
||||
}
|
||||
|
||||
private handleRefreshNetworkError(sessionId: string, refreshToken: string, scope: string, attempts: number = 1): Promise<boolean> {
|
||||
return new Promise((resolve, _) => {
|
||||
if (attempts === 3) {
|
||||
Logger.error(`Token refresh (for scopes: ${scope}) failed after 3 attempts`);
|
||||
return resolve(false);
|
||||
}
|
||||
|
||||
const delayBeforeRetry = 5 * attempts * attempts;
|
||||
|
||||
this.clearSessionTimeout(sessionId);
|
||||
|
||||
this._refreshTimeouts.set(sessionId, setTimeout(async () => {
|
||||
try {
|
||||
const refreshedToken = await this.refreshToken(refreshToken, scope, sessionId);
|
||||
onDidChangeSessions.fire({ added: [], removed: [], changed: [this.convertToSessionSync(refreshedToken)] });
|
||||
return resolve(true);
|
||||
} catch (e) {
|
||||
return resolve(await this.handleRefreshNetworkError(sessionId, refreshToken, scope, attempts + 1));
|
||||
}
|
||||
}, 1000 * delayBeforeRetry));
|
||||
});
|
||||
}
|
||||
|
||||
public async removeSession(sessionId: string): Promise<vscode.AuthenticationSession | undefined> {
|
||||
Logger.info(`Logging out of session '${sessionId}'`);
|
||||
const token = this.removeInMemorySessionData(sessionId);
|
||||
|
|
|
@ -168,7 +168,7 @@ export const keywords: IEntries = {
|
|||
description: 'This language construct is equivalent to exit().',
|
||||
},
|
||||
echo: {
|
||||
description: 'Outputs all parameters. \r\n\r\necho() is not actually a function (it is a language construct), so you are not required to use parentheses with it. echo() (unlike some other language constructs) does not behave like a function, so it cannot always be used in the context of a function. Additionally, if you want to pass more than one parameter to echo(), the parameters must not be enclosed within parentheses.\r\n\r\necho() also has a shortcut syntax, where you can immediately follow the opening tag with an equals sign. This short syntax only works with the short_open_tag configuration setting enabled.',
|
||||
description: 'Outputs all parameters. \r\n\r\necho is not actually a function (it is a language construct), so you are not required to use parentheses with it. echo (unlike some other language constructs) does not behave like a function, so it cannot always be used in the context of a function. Additionally, if you want to pass more than one parameter to echo, the parameters must not be enclosed within parentheses.\r\n\r\necho also has a shortcut syntax, where you can immediately follow the opening tag with an equals sign. This short syntax only works with the short_open_tag configuration setting enabled.',
|
||||
signature: '( string $arg1 [, string $... ] ): void'
|
||||
},
|
||||
empty: {
|
||||
|
@ -184,10 +184,10 @@ export const keywords: IEntries = {
|
|||
signature: '( string $code_str ): mixed'
|
||||
},
|
||||
include: {
|
||||
description: 'The include() statement includes and evaluates the specified file.',
|
||||
description: 'The include statement includes and evaluates the specified file.',
|
||||
},
|
||||
include_once: {
|
||||
description: 'The include_once() statement includes and evaluates the specified file during the execution of the script. This is a behavior similar to the include() statement, with the only difference being that if the code from a file has already been included, it will not be included again. As the name suggests, it will be included just once. \r\n\r\ninclude_once() may be used in cases where the same file might be included and evaluated more than once during a particular execution of a script, so in this case it may help avoid problems such as function redefinitions, variable value reassignments, etc.',
|
||||
description: 'The include_once statement includes and evaluates the specified file during the execution of the script. This is a behavior similar to the include statement, with the only difference being that if the code from a file has already been included, it will not be included again. As the name suggests, it will be included just once. \r\n\r\ninclude_once may be used in cases where the same file might be included and evaluated more than once during a particular execution of a script, so in this case it may help avoid problems such as function redefinitions, variable value reassignments, etc.',
|
||||
},
|
||||
isset: {
|
||||
description: 'Determine if a variable is set and is not NULL. \r\n\r\nIf a variable has been unset with unset(), it will no longer be set. isset() will return FALSE if testing a variable that has been set to NULL. Also note that a NULL byte is not equivalent to the PHP NULL constant. \r\n\r\nIf multiple parameters are supplied then isset() will return TRUE only if all of the parameters are set. Evaluation goes from left to right and stops as soon as an unset variable is encountered.',
|
||||
|
@ -198,13 +198,13 @@ export const keywords: IEntries = {
|
|||
signature: '( mixed $varname [, mixed $... ] ): array'
|
||||
},
|
||||
require: {
|
||||
description: 'require() is identical to include() except upon failure it will also produce a fatal E_COMPILE_ERROR level error. In other words, it will halt the script whereas include() only emits a warning (E_WARNING) which allows the script to continue.',
|
||||
description: 'require is identical to include except upon failure it will also produce a fatal E_COMPILE_ERROR level error. In other words, it will halt the script whereas include only emits a warning (E_WARNING) which allows the script to continue.',
|
||||
},
|
||||
require_once: {
|
||||
description: 'The require_once() statement is identical to require() except PHP will check if the file has already been included, and if so, not include (require) it again.',
|
||||
description: 'The require_once statement is identical to require except PHP will check if the file has already been included, and if so, not include (require) it again.',
|
||||
},
|
||||
return: {
|
||||
description: 'If called from within a function, the return() statement immediately ends execution of the current function, and returns its argument as the value of the function call. return() will also end the execution of an eval() statement or script file. \r\n\r\nIf called from the global scope, then execution of the current script file is ended. If the current script file was include()ed or require()ed, then control is passed back to the calling file. Furthermore, if the current script file was include()ed, then the value given to return() will be returned as the value of the include() call. If return() is called from within the main script file, then script execution ends. If the current script file was named by the auto_prepend_file or auto_append_file configuration options in php.ini, then that script file\'s execution is ended.',
|
||||
description: 'If called from within a function, the return statement immediately ends execution of the current function, and returns its argument as the value of the function call. return will also end the execution of an eval() statement or script file. \r\n\r\nIf called from the global scope, then execution of the current script file is ended. If the current script file was included or required, then control is passed back to the calling file. Furthermore, if the current script file was included, then the value given to return will be returned as the value of the include call. If return is called from within the main script file, then script execution ends. If the current script file was named by the auto_prepend_file or auto_append_file configuration options in php.ini, then that script file\'s execution is ended.',
|
||||
},
|
||||
print: {
|
||||
description: 'Outputs arg. \r\n\r\nprint() is not actually a real function (it is a language construct) so you are not required to use parentheses with its argument list.',
|
||||
|
@ -321,4 +321,4 @@ export const keywords: IEntries = {
|
|||
},
|
||||
xor: {
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
"enabledApiProposals": [
|
||||
"inlayHints",
|
||||
"languageStatus",
|
||||
"quickPickSeparators",
|
||||
"resolvers",
|
||||
"workspaceTrust"
|
||||
],
|
||||
|
@ -1115,16 +1116,16 @@
|
|||
"markdownDescription": "%typescript.workspaceSymbols.scope%",
|
||||
"scope": "window"
|
||||
},
|
||||
"javascript.suggest.includeCompletionsWithClassMemberSnippets": {
|
||||
"javascript.suggest.classMemberSnippets.enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "%configuration.suggest.includeCompletionsWithClassMemberSnippets%",
|
||||
"description": "%configuration.suggest.classMemberSnippets.enabled%",
|
||||
"scope": "resource"
|
||||
},
|
||||
"typescript.suggest.includeCompletionsWithClassMemberSnippets": {
|
||||
"typescript.suggest.classMemberSnippets.enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "%configuration.suggest.includeCompletionsWithClassMemberSnippets%",
|
||||
"description": "%configuration.suggest.classMemberSnippets.enabled%",
|
||||
"scope": "resource"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -184,5 +184,5 @@
|
|||
"codeActions.refactor.rewrite.property.generateAccessors.description": "Generate 'get' and 'set' accessors",
|
||||
"codeActions.source.organizeImports.title": "Organize imports",
|
||||
"typescript.findAllFileReferences": "Find File References",
|
||||
"configuration.suggest.includeCompletionsWithClassMemberSnippets": "Enable/disable snippet completions for class members. Requires using TypeScript 4.5+ in the workspace"
|
||||
"configuration.suggest.classMemberSnippets.enabled": "Enable/disable snippet completions for class members. Requires using TypeScript 4.5+ in the workspace"
|
||||
}
|
||||
|
|
|
@ -186,7 +186,7 @@ export default class FileConfigurationManager extends Disposable {
|
|||
generateReturnInDocTemplate: config.get<boolean>('suggest.jsdoc.generateReturns', true),
|
||||
includeCompletionsForImportStatements: config.get<boolean>('suggest.includeCompletionsForImportStatements', true),
|
||||
includeCompletionsWithSnippetText: config.get<boolean>('suggest.includeCompletionsWithSnippetText', true),
|
||||
includeCompletionsWithClassMemberSnippets: config.get<boolean>('suggest.includeCompletionsWithClassMemberSnippets', true),
|
||||
includeCompletionsWithClassMemberSnippets: config.get<boolean>('suggest.classMemberSnippets.enabled', true),
|
||||
allowIncompleteCompletions: true,
|
||||
displayPartsForJSDoc: true,
|
||||
...getInlayHintsPreferences(config),
|
||||
|
|
|
@ -256,21 +256,15 @@ export class TypeScriptServerSpawner {
|
|||
}
|
||||
}
|
||||
|
||||
if (configuration.npmLocation) {
|
||||
if (configuration.npmLocation && !isWeb()) {
|
||||
args.push('--npmLocation', `"${configuration.npmLocation}"`);
|
||||
}
|
||||
|
||||
if (apiVersion.gte(API.v260)) {
|
||||
args.push('--locale', TypeScriptServerSpawner.getTsLocale(configuration));
|
||||
}
|
||||
args.push('--locale', TypeScriptServerSpawner.getTsLocale(configuration));
|
||||
|
||||
if (apiVersion.gte(API.v291)) {
|
||||
args.push('--noGetErrOnBackgroundUpdate');
|
||||
}
|
||||
args.push('--noGetErrOnBackgroundUpdate');
|
||||
|
||||
if (apiVersion.gte(API.v345)) {
|
||||
args.push('--validateDefaultNpmLocation');
|
||||
}
|
||||
args.push('--validateDefaultNpmLocation');
|
||||
|
||||
return { args, tsServerLogFile, tsServerTraceDirectory };
|
||||
}
|
||||
|
|
|
@ -82,6 +82,11 @@ export class TypeScriptVersionManager extends Disposable {
|
|||
const selected = await vscode.window.showQuickPick<QuickPickItem>([
|
||||
this.getBundledPickItem(),
|
||||
...this.getLocalPickItems(),
|
||||
{
|
||||
kind: vscode.QuickPickItemKind.Separator,
|
||||
label: '',
|
||||
run: () => { /* noop */ },
|
||||
},
|
||||
LearnMorePickItem,
|
||||
], {
|
||||
placeHolder: localize(
|
||||
|
@ -180,7 +185,7 @@ export class TypeScriptVersionManager extends Disposable {
|
|||
}
|
||||
|
||||
const LearnMorePickItem: QuickPickItem = {
|
||||
label: localize('learnMore', 'Learn more about managing TypeScript versions'),
|
||||
label: localize('learnMore', "Learn more about managing TypeScript versions"),
|
||||
description: '',
|
||||
run: () => {
|
||||
vscode.env.openExternal(vscode.Uri.parse('https://go.microsoft.com/fwlink/?linkid=839919'));
|
||||
|
|
|
@ -90,8 +90,7 @@ function getTagDocumentation(
|
|||
if (!doc) {
|
||||
return label;
|
||||
}
|
||||
// allow-any-unicode-next-line
|
||||
return label + (doc.match(/\r\n|\n/g) ? ' \n' + processInlineTags(doc) : ` — ${processInlineTags(doc)}`);
|
||||
return label + (doc.match(/\r\n|\n/g) ? ' \n' + processInlineTags(doc) : ` \u2014 ${processInlineTags(doc)}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,8 +100,7 @@ function getTagDocumentation(
|
|||
if (!text) {
|
||||
return label;
|
||||
}
|
||||
// allow-any-unicode-next-line
|
||||
return label + (text.match(/\r\n|\n/g) ? ' \n' + text : ` — ${text}`);
|
||||
return label + (text.match(/\r\n|\n/g) ? ' \n' + text : ` \u2014 ${text}`);
|
||||
}
|
||||
|
||||
export function plainWithLinks(
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
"../../src/vscode-dts/vscode.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.inlayHints.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.languageStatus.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.quickPickSeparators.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.resolvers.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.workspaceTrust.d.ts",
|
||||
]
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
"license": "MIT",
|
||||
"enabledApiProposals": [
|
||||
"authSession",
|
||||
"contribViewsRemote",
|
||||
"customEditorMove",
|
||||
"diffCommand",
|
||||
"documentFiltersExclusive",
|
||||
|
|
12
package.json
12
package.json
|
@ -27,7 +27,7 @@
|
|||
"watch-extensions": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js watch-extensions watch-extension-media",
|
||||
"watch-extensionsd": "deemon yarn watch-extensions",
|
||||
"kill-watch-extensionsd": "deemon --kill yarn watch-extensions",
|
||||
"mocha": "mocha test/unit/node/all.js --delay",
|
||||
"mocha": "mocha test/unit/node/all.js --delay --ui=tdd",
|
||||
"precommit": "node build/hygiene.js",
|
||||
"gulp": "node --max_old_space_size=8192 ./node_modules/gulp/bin/gulp.js",
|
||||
"electron": "node build/lib/electron",
|
||||
|
@ -59,15 +59,15 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@microsoft/applicationinsights-web": "^2.6.4",
|
||||
"@parcel/watcher": "2.0.2",
|
||||
"@parcel/watcher": "2.0.3",
|
||||
"@vscode/sqlite3": "4.0.12",
|
||||
"@vscode/sudo-prompt": "^9.3.0",
|
||||
"@vscode/sudo-prompt": "9.3.1",
|
||||
"@vscode/vscode-languagedetection": "1.0.21",
|
||||
"applicationinsights": "1.0.8",
|
||||
"graceful-fs": "4.2.8",
|
||||
"http-proxy-agent": "^2.1.0",
|
||||
"https-proxy-agent": "^2.2.3",
|
||||
"iconv-lite-umd": "0.6.8",
|
||||
"iconv-lite-umd": "0.6.10",
|
||||
"jschardet": "3.0.0",
|
||||
"keytar": "7.2.0",
|
||||
"minimist": "^1.2.5",
|
||||
|
@ -140,8 +140,8 @@
|
|||
"file-loader": "^4.2.0",
|
||||
"glob": "^5.0.13",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-atom-electron": "1.32.0",
|
||||
"gulp-azure-storage": "^0.11.1",
|
||||
"gulp-atom-electron": "^1.32.1",
|
||||
"gulp-azure-storage": "^0.12.1",
|
||||
"gulp-bom": "^3.0.0",
|
||||
"gulp-buffer": "0.0.2",
|
||||
"gulp-concat": "^2.6.1",
|
||||
|
|
|
@ -4,14 +4,14 @@
|
|||
"private": true,
|
||||
"dependencies": {
|
||||
"@microsoft/applicationinsights-web": "^2.6.4",
|
||||
"@parcel/watcher": "2.0.2",
|
||||
"@parcel/watcher": "2.0.3",
|
||||
"@vscode/vscode-languagedetection": "1.0.21",
|
||||
"applicationinsights": "1.0.8",
|
||||
"cookie": "^0.4.0",
|
||||
"graceful-fs": "4.2.8",
|
||||
"http-proxy-agent": "^2.1.0",
|
||||
"https-proxy-agent": "^2.2.3",
|
||||
"iconv-lite-umd": "0.6.8",
|
||||
"iconv-lite-umd": "0.6.10",
|
||||
"jschardet": "3.0.0",
|
||||
"minimist": "^1.2.5",
|
||||
"native-watchdog": "1.3.0",
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"dependencies": {
|
||||
"@microsoft/applicationinsights-web": "^2.6.4",
|
||||
"@vscode/vscode-languagedetection": "1.0.21",
|
||||
"iconv-lite-umd": "0.6.8",
|
||||
"iconv-lite-umd": "0.6.10",
|
||||
"jschardet": "3.0.0",
|
||||
"tas-client-umd": "0.1.4",
|
||||
"vscode-oniguruma": "1.6.1",
|
||||
|
|
|
@ -88,10 +88,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@vscode/vscode-languagedetection/-/vscode-languagedetection-1.0.21.tgz#89b48f293f6aa3341bb888c1118d16ff13b032d3"
|
||||
integrity sha512-zSUH9HYCw5qsCtd7b31yqkpaCU6jhtkKLkvOOA8yTrIRfBSOFb8PPhgmMicD7B/m+t4PwOJXzU1XDtrM9Fd3/g==
|
||||
|
||||
iconv-lite-umd@0.6.8:
|
||||
version "0.6.8"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.8.tgz#5ad310ec126b260621471a2d586f7f37b9958ec0"
|
||||
integrity sha512-zvXJ5gSwMC9JD3wDzH8CoZGc1pbiJn12Tqjk8BXYCnYz3hYL5GRjHW8LEykjXhV9WgNGI4rgpgHcbIiBfrRq6A==
|
||||
iconv-lite-umd@0.6.10:
|
||||
version "0.6.10"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.10.tgz#faec47521e095b8e3a7175ae08e1b4ae0359a735"
|
||||
integrity sha512-8NtgTa/m1jVq7vdywmD5+SqIlZsB59wtsjaylQuExyCojMq1tHVQxmHjeqVSYwKwnmQbH4mZ1Dxx1eqDkPgaqA==
|
||||
|
||||
jschardet@3.0.0:
|
||||
version "3.0.0"
|
||||
|
|
|
@ -83,10 +83,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.4.tgz#40e1c0ad20743fcee1604a7df2c57faf0aa1af87"
|
||||
integrity sha512-Ot53G927ykMF8cQ3/zq4foZtdk+Tt1YpX7aUTHxBU7UHNdkEiBvBfZSq+rnlUmKCJ19VatwPG4mNzvcGpBj4og==
|
||||
|
||||
"@parcel/watcher@2.0.2":
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.2.tgz#46bef14584497147bad5247cfb41f80b24d24dfb"
|
||||
integrity sha512-WGJY55/mTAGse2C9VVi2oo+p05oJ0kiSHmOjV33+ywgKgUkUh6B/qFQ5kBO/9mH686qqtV3k2zH1QNm+XX4+lw==
|
||||
"@parcel/watcher@2.0.3":
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.3.tgz#2bae7720f2b9c21ea0b89bab55479c7e8937231e"
|
||||
integrity sha512-PHh5PArr3nYGYVj9z/NSfDmmKEBNrg2bzoFgxzjTRBBxPUKx039x3HF6VGLFIfrghjJxcYn/IeSpdVwfob7KFA==
|
||||
dependencies:
|
||||
node-addon-api "^3.2.1"
|
||||
node-gyp-build "^4.3.0"
|
||||
|
@ -305,10 +305,10 @@ https-proxy-agent@^5.0.0:
|
|||
agent-base "6"
|
||||
debug "4"
|
||||
|
||||
iconv-lite-umd@0.6.8:
|
||||
version "0.6.8"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.8.tgz#5ad310ec126b260621471a2d586f7f37b9958ec0"
|
||||
integrity sha512-zvXJ5gSwMC9JD3wDzH8CoZGc1pbiJn12Tqjk8BXYCnYz3hYL5GRjHW8LEykjXhV9WgNGI4rgpgHcbIiBfrRq6A==
|
||||
iconv-lite-umd@0.6.10:
|
||||
version "0.6.10"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.10.tgz#faec47521e095b8e3a7175ae08e1b4ae0359a735"
|
||||
integrity sha512-8NtgTa/m1jVq7vdywmD5+SqIlZsB59wtsjaylQuExyCojMq1tHVQxmHjeqVSYwKwnmQbH4mZ1Dxx1eqDkPgaqA==
|
||||
|
||||
inherits@~2.0.1:
|
||||
version "2.0.4"
|
||||
|
|
|
@ -61,7 +61,7 @@ if (ENABLE_SYNC) {
|
|||
}
|
||||
|
||||
// Connection Token
|
||||
serverArgs.push('--connectionToken', '00000');
|
||||
serverArgs.push('--connection-token', '00000');
|
||||
|
||||
// Server should really only listen from localhost
|
||||
serverArgs.push('--host', '127.0.0.1');
|
||||
|
|
|
@ -37,7 +37,7 @@ const ALLOWED_CORS_ORIGINS = [
|
|||
'http://127.0.0.1:8080',
|
||||
];
|
||||
|
||||
const WEB_PLAYGROUND_VERSION = '0.0.12';
|
||||
const WEB_PLAYGROUND_VERSION = '0.0.13';
|
||||
|
||||
const args = minimist(process.argv, {
|
||||
boolean: [
|
||||
|
|
|
@ -978,6 +978,8 @@ class FocusTracker extends Disposable implements IFocusTracker {
|
|||
|
||||
this._register(addDisposableListener(element, EventType.FOCUS, onFocus, true));
|
||||
this._register(addDisposableListener(element, EventType.BLUR, onBlur, true));
|
||||
this._register(addDisposableListener(element, EventType.FOCUS_IN, () => this._refreshStateHandler()));
|
||||
this._register(addDisposableListener(element, EventType.FOCUS_OUT, () => this._refreshStateHandler()));
|
||||
}
|
||||
|
||||
refreshState() {
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
float: left;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
opacity: 0.7;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid transparent;
|
||||
padding: 1px;
|
||||
box-sizing: border-box;
|
||||
|
@ -19,9 +19,12 @@
|
|||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
.monaco-custom-checkbox:hover,
|
||||
.monaco-custom-checkbox.checked {
|
||||
opacity: 1;
|
||||
.monaco-custom-checkbox:hover {
|
||||
background-color: var(--vscode-inputOption-hoverBackground);
|
||||
}
|
||||
|
||||
.hc-black .monaco-custom-checkbox:hover {
|
||||
border: 1px dashed var(--vscode-focusBorder);
|
||||
}
|
||||
|
||||
.hc-black .monaco-custom-checkbox {
|
||||
|
|
|
@ -191,9 +191,9 @@ export class Checkbox extends Widget {
|
|||
|
||||
protected applyStyles(): void {
|
||||
if (this.domNode) {
|
||||
this.domNode.style.borderColor = this._checked && this._opts.inputActiveOptionBorder ? this._opts.inputActiveOptionBorder.toString() : 'transparent';
|
||||
this.domNode.style.borderColor = this._checked && this._opts.inputActiveOptionBorder ? this._opts.inputActiveOptionBorder.toString() : '';
|
||||
this.domNode.style.color = this._checked && this._opts.inputActiveOptionForeground ? this._opts.inputActiveOptionForeground.toString() : 'inherit';
|
||||
this.domNode.style.backgroundColor = this._checked && this._opts.inputActiveOptionBackground ? this._opts.inputActiveOptionBackground.toString() : 'transparent';
|
||||
this.domNode.style.backgroundColor = this._checked && this._opts.inputActiveOptionBackground ? this._opts.inputActiveOptionBackground.toString() : '';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -178,10 +178,7 @@ export class ActionWithDropdownActionViewItem extends ActionViewItem {
|
|||
const menuActionsProvider = {
|
||||
getActions: () => {
|
||||
const actionsProvider = (<IActionWithDropdownActionViewItemOptions>this.options).menuActionsOrProvider;
|
||||
return [this._action, ...(Array.isArray(actionsProvider)
|
||||
? actionsProvider
|
||||
: (actionsProvider as IActionProvider).getActions()) // TODO: microsoft/TypeScript#42768
|
||||
];
|
||||
return Array.isArray(actionsProvider) ? actionsProvider : (actionsProvider as IActionProvider).getActions(); // TODO: microsoft/TypeScript#42768
|
||||
}
|
||||
};
|
||||
this.dropdownMenuActionViewItem = new DropdownMenuActionViewItem(this._register(new Action('dropdownAction', undefined)), menuActionsProvider, this.contextMenuProvider, { classNames: ['dropdown', ...Codicon.dropDownButton.classNamesArray, ...(<IActionWithDropdownActionViewItemOptions>this.options).menuActionClassNames || []] });
|
||||
|
|
|
@ -254,7 +254,7 @@ export function topAsync<T>(array: T[], compare: (a: T, b: T) => number, n: numb
|
|||
const result = array.slice(0, n).sort(compare);
|
||||
for (let i = n, m = Math.min(n + batch, o); i < o; i = m, m = Math.min(m + batch, o)) {
|
||||
if (i > n) {
|
||||
await new Promise(resolve => setTimeout(resolve)); // nextTick() would starve I/O.
|
||||
await new Promise(resolve => setTimeout(resolve)); // any other delay function would starve I/O
|
||||
}
|
||||
if (token && token.isCancellationRequested) {
|
||||
throw canceled();
|
||||
|
|
|
@ -9,6 +9,7 @@ import { Emitter, Event } from 'vs/base/common/event';
|
|||
import { Disposable, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { extUri as defaultExtUri, IExtUri } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { setTimeout0 } from 'vs/base/common/platform';
|
||||
|
||||
export function isThenable<T>(obj: unknown): obj is Promise<T> {
|
||||
return !!obj && typeof (obj as unknown as Promise<T>).then === 'function';
|
||||
|
@ -968,6 +969,7 @@ export interface IdleDeadline {
|
|||
readonly didTimeout: boolean;
|
||||
timeRemaining(): number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the callback the next time the browser is idle
|
||||
*/
|
||||
|
@ -979,8 +981,11 @@ declare function cancelIdleCallback(handle: number): void;
|
|||
(function () {
|
||||
if (typeof requestIdleCallback !== 'function' || typeof cancelIdleCallback !== 'function') {
|
||||
runWhenIdle = (runner) => {
|
||||
const handle = setTimeout(() => {
|
||||
const end = Date.now() + 15; // one frame at 64fps
|
||||
setTimeout0(() => {
|
||||
if (disposed) {
|
||||
return;
|
||||
}
|
||||
const end = Date.now() + 3; // yield often
|
||||
runner(Object.freeze({
|
||||
didTimeout: true,
|
||||
timeRemaining() {
|
||||
|
@ -995,7 +1000,6 @@ declare function cancelIdleCallback(handle: number): void;
|
|||
return;
|
||||
}
|
||||
disposed = true;
|
||||
clearTimeout(handle);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -1199,10 +1203,10 @@ export class IntervalCounter {
|
|||
|
||||
private value = 0;
|
||||
|
||||
constructor(private readonly interval: number) { }
|
||||
constructor(private readonly interval: number, private readonly nowFn = () => Date.now()) { }
|
||||
|
||||
increment(): number {
|
||||
const now = Date.now();
|
||||
const now = this.nowFn();
|
||||
|
||||
// We are outside of the range of `interval` and as such
|
||||
// start counting from 0 and remember the time
|
||||
|
|
|
@ -198,7 +198,7 @@ export namespace Event {
|
|||
/**
|
||||
* @deprecated DO NOT use, this leaks memory
|
||||
*/
|
||||
export function buffer<T>(event: Event<T>, nextTick = false, _buffer: T[] = []): Event<T> {
|
||||
export function buffer<T>(event: Event<T>, flushAfterTimeout = false, _buffer: T[] = []): Event<T> {
|
||||
let buffer: T[] | null = _buffer.slice();
|
||||
|
||||
let listener: IDisposable | null = event(e => {
|
||||
|
@ -225,7 +225,7 @@ export namespace Event {
|
|||
|
||||
onFirstListenerDidAdd() {
|
||||
if (buffer) {
|
||||
if (nextTick) {
|
||||
if (flushAfterTimeout) {
|
||||
setTimeout(flush);
|
||||
} else {
|
||||
flush();
|
||||
|
|
|
@ -54,8 +54,7 @@ export class ModifierLabelProvider {
|
|||
*/
|
||||
export const UILabelProvider = new ModifierLabelProvider(
|
||||
{
|
||||
// allow-any-unicode-next-line
|
||||
ctrlKey: '⌃',
|
||||
ctrlKey: '\u2303',
|
||||
shiftKey: '⇧',
|
||||
altKey: '⌥',
|
||||
metaKey: '⌘',
|
||||
|
|
|
@ -40,7 +40,9 @@
|
|||
*/
|
||||
function _define() {
|
||||
|
||||
if (typeof performance === 'object' && typeof performance.mark === 'function') {
|
||||
// Identify browser environment when following property is not present
|
||||
// https://nodejs.org/dist/latest-v16.x/docs/api/perf_hooks.html#performancenodetiming
|
||||
if (typeof performance === 'object' && typeof performance.mark === 'function' && !performance.nodeTiming) {
|
||||
// in a browser context, reuse performance-util
|
||||
|
||||
if (typeof performance.timeOrigin !== 'number' && !performance.timing) {
|
||||
|
|
|
@ -38,7 +38,6 @@ export interface INodeProcess {
|
|||
platform: string;
|
||||
arch: string;
|
||||
env: IProcessEnvironment;
|
||||
nextTick?: (callback: (...args: any[]) => void) => void;
|
||||
versions?: {
|
||||
electron?: string;
|
||||
};
|
||||
|
@ -186,14 +185,13 @@ export const locale = _locale;
|
|||
*/
|
||||
export const translationsConfigFile = _translationsConfigFile;
|
||||
|
||||
interface ISetImmediate {
|
||||
(callback: (...args: unknown[]) => void): void;
|
||||
}
|
||||
|
||||
export const setImmediate: ISetImmediate = (function defineSetImmediate() {
|
||||
if (globals.setImmediate) {
|
||||
return globals.setImmediate.bind(globals);
|
||||
}
|
||||
/**
|
||||
* See https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#:~:text=than%204%2C%20then-,set%20timeout%20to%204,-.
|
||||
*
|
||||
* Works similarly to `setTimeout(0)` but doesn't suffer from the 4ms artificial delay
|
||||
* that browsers set when the nesting level is > 5.
|
||||
*/
|
||||
export const setTimeout0 = (() => {
|
||||
if (typeof globals.postMessage === 'function' && !globals.importScripts) {
|
||||
interface IQueueElement {
|
||||
id: number;
|
||||
|
@ -201,10 +199,10 @@ export const setImmediate: ISetImmediate = (function defineSetImmediate() {
|
|||
}
|
||||
let pending: IQueueElement[] = [];
|
||||
globals.addEventListener('message', (e: MessageEvent) => {
|
||||
if (e.data && e.data.vscodeSetImmediateId) {
|
||||
if (e.data && e.data.vscodeScheduleAsyncWork) {
|
||||
for (let i = 0, len = pending.length; i < len; i++) {
|
||||
const candidate = pending[i];
|
||||
if (candidate.id === e.data.vscodeSetImmediateId) {
|
||||
if (candidate.id === e.data.vscodeScheduleAsyncWork) {
|
||||
pending.splice(i, 1);
|
||||
candidate.callback();
|
||||
return;
|
||||
|
@ -219,14 +217,10 @@ export const setImmediate: ISetImmediate = (function defineSetImmediate() {
|
|||
id: myId,
|
||||
callback: callback
|
||||
});
|
||||
globals.postMessage({ vscodeSetImmediateId: myId }, '*');
|
||||
globals.postMessage({ vscodeScheduleAsyncWork: myId }, '*');
|
||||
};
|
||||
}
|
||||
if (typeof nodeProcess?.nextTick === 'function') {
|
||||
return nodeProcess.nextTick.bind(nodeProcess);
|
||||
}
|
||||
const _promise = Promise.resolve();
|
||||
return (callback: (...args: unknown[]) => void) => _promise.then(callback);
|
||||
return (callback: () => void) => setTimeout(callback);
|
||||
})();
|
||||
|
||||
export const enum OperatingSystem {
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { globals, INodeProcess, isMacintosh, isWindows, setImmediate } from 'vs/base/common/platform';
|
||||
import { globals, INodeProcess, isMacintosh, isWindows } from 'vs/base/common/platform';
|
||||
|
||||
let safeProcess: Omit<INodeProcess, 'arch'> & { nextTick: (callback: (...args: any[]) => void) => void; arch: string | undefined; };
|
||||
let safeProcess: Omit<INodeProcess, 'arch'> & { arch: string | undefined; };
|
||||
declare const process: INodeProcess;
|
||||
|
||||
// Native sandbox environment
|
||||
|
@ -15,8 +15,7 @@ if (typeof globals.vscode !== 'undefined' && typeof globals.vscode.process !== '
|
|||
get platform() { return sandboxProcess.platform; },
|
||||
get arch() { return sandboxProcess.arch; },
|
||||
get env() { return sandboxProcess.env; },
|
||||
cwd() { return sandboxProcess.cwd(); },
|
||||
nextTick(callback: (...args: any[]) => void): void { return setImmediate(callback); }
|
||||
cwd() { return sandboxProcess.cwd(); }
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -26,8 +25,7 @@ else if (typeof process !== 'undefined') {
|
|||
get platform() { return process.platform; },
|
||||
get arch() { return process.arch; },
|
||||
get env() { return process.env; },
|
||||
cwd() { return process.env['VSCODE_CWD'] || process.cwd(); },
|
||||
nextTick(callback: (...args: any[]) => void): void { return process.nextTick!(callback); }
|
||||
cwd() { return process.env['VSCODE_CWD'] || process.cwd(); }
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -38,7 +36,6 @@ else {
|
|||
// Supported
|
||||
get platform() { return isWindows ? 'win32' : isMacintosh ? 'darwin' : 'linux'; },
|
||||
get arch() { return undefined; /* arch is undefined in web */ },
|
||||
nextTick(callback: (...args: any[]) => void): void { return setImmediate(callback); },
|
||||
|
||||
// Unsupported
|
||||
get env() { return {}; },
|
||||
|
@ -68,12 +65,6 @@ export const env = safeProcess.env;
|
|||
*/
|
||||
export const platform = safeProcess.platform;
|
||||
|
||||
/**
|
||||
* Provides safe access to the `nextTick` method in node.js, sandboxed or web
|
||||
* environments.
|
||||
*/
|
||||
export const nextTick = safeProcess.nextTick;
|
||||
|
||||
/**
|
||||
* Provides safe access to the `arch` method in node.js, sandboxed or web
|
||||
* environments.
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -48,18 +48,15 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
|
|||
|
||||
function findName(cmd: string): string {
|
||||
|
||||
const SHARED_PROCESS_HINT = /--disable-blink-features=Auxclick/;
|
||||
const WINDOWS_WATCHER_HINT = /\\watcher\\win32\\CodeHelper\.exe/;
|
||||
const SHARED_PROCESS_HINT = /--vscode-window-kind=shared-process/;
|
||||
const ISSUE_REPORTER_HINT = /--vscode-window-kind=issue-reporter/;
|
||||
const PROCESS_EXPLORER_HINT = /--vscode-window-kind=process-explorer/;
|
||||
const UTILITY_NETWORK_HINT = /--utility-sub-type=network/;
|
||||
const WINDOWS_CRASH_REPORTER = /--crashes-directory/;
|
||||
const WINDOWS_PTY = /\\pipe\\winpty-control/;
|
||||
const WINDOWS_CONSOLE_HOST = /conhost\.exe/;
|
||||
const TYPE = /--type=([a-zA-Z-]+)/;
|
||||
|
||||
// find windows file watcher
|
||||
if (WINDOWS_WATCHER_HINT.exec(cmd)) {
|
||||
return 'watcherService ';
|
||||
}
|
||||
|
||||
// find windows crash reporter
|
||||
if (WINDOWS_CRASH_REPORTER.exec(cmd)) {
|
||||
return 'electron-crash-reporter';
|
||||
|
@ -83,7 +80,19 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
|
|||
return 'shared-process';
|
||||
}
|
||||
|
||||
if (ISSUE_REPORTER_HINT.exec(cmd)) {
|
||||
return 'issue-reporter';
|
||||
}
|
||||
|
||||
if (PROCESS_EXPLORER_HINT.exec(cmd)) {
|
||||
return 'process-explorer';
|
||||
}
|
||||
|
||||
return `window`;
|
||||
} else if (matches[1] === 'utility') {
|
||||
if (UTILITY_NETWORK_HINT.exec(cmd)) {
|
||||
return 'utility-network-service';
|
||||
}
|
||||
}
|
||||
return matches[1];
|
||||
}
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import * as process from 'vs/base/common/process';
|
||||
import { IIPCLogger, IMessagePassingProtocol, IPCClient } from 'vs/base/parts/ipc/common/ipc';
|
||||
|
||||
export const enum SocketCloseEventType {
|
||||
|
@ -370,7 +368,7 @@ class ProtocolWriter {
|
|||
|
||||
private _writeSoon(header: VSBuffer, data: VSBuffer): void {
|
||||
if (this._bufferAdd(header, data)) {
|
||||
platform.setImmediate(() => {
|
||||
setTimeout(() => {
|
||||
this._writeNow();
|
||||
});
|
||||
}
|
||||
|
@ -483,8 +481,8 @@ export class BufferedEmitter<T> {
|
|||
this._hasListeners = true;
|
||||
// it is important to deliver these messages after this call, but before
|
||||
// other messages have a chance to be received (to guarantee in order delivery)
|
||||
// that's why we're using here nextTick and not other types of timeouts
|
||||
process.nextTick(() => this._deliverMessages());
|
||||
// that's why we're using here queueMicrotask and not other types of timeouts
|
||||
queueMicrotask(() => this._deliverMessages());
|
||||
},
|
||||
onLastListenerRemove: () => {
|
||||
this._hasListeners = false;
|
||||
|
|
|
@ -740,25 +740,14 @@ suite('Async', () => {
|
|||
});
|
||||
|
||||
test('IntervalCounter', async () => {
|
||||
let now = Date.now();
|
||||
|
||||
const counter = new async.IntervalCounter(5);
|
||||
|
||||
let ellapsed = Date.now() - now;
|
||||
if (ellapsed > 4) {
|
||||
return; // flaky (https://github.com/microsoft/vscode/issues/114028)
|
||||
}
|
||||
let now = 0;
|
||||
const counter = new async.IntervalCounter(5, () => now);
|
||||
|
||||
assert.strictEqual(counter.increment(), 1);
|
||||
assert.strictEqual(counter.increment(), 2);
|
||||
assert.strictEqual(counter.increment(), 3);
|
||||
|
||||
now = Date.now();
|
||||
await async.timeout(10);
|
||||
ellapsed = Date.now() - now;
|
||||
if (ellapsed < 5) {
|
||||
return; // flaky (https://github.com/microsoft/vscode/issues/114028)
|
||||
}
|
||||
now = 10;
|
||||
|
||||
assert.strictEqual(counter.increment(), 1);
|
||||
assert.strictEqual(counter.increment(), 2);
|
||||
|
|
|
@ -263,7 +263,7 @@ export const originalGlobalValues = {
|
|||
Date: globalThis.Date,
|
||||
};
|
||||
|
||||
function setTimeout(scheduler: Scheduler, handler: TimerHandler, timeout: number): IDisposable {
|
||||
function setTimeout(scheduler: Scheduler, handler: TimerHandler, timeout: number = 0): IDisposable {
|
||||
if (typeof handler === 'string') {
|
||||
throw new Error('String handler args should not be used and are not supported');
|
||||
}
|
||||
|
@ -324,7 +324,7 @@ function setInterval(scheduler: Scheduler, handler: TimerHandler, interval: numb
|
|||
}
|
||||
|
||||
function overwriteGlobals(scheduler: Scheduler): IDisposable {
|
||||
globalThis.setTimeout = ((handler: TimerHandler, timeout: number) => setTimeout(scheduler, handler, timeout)) as any;
|
||||
globalThis.setTimeout = ((handler: TimerHandler, timeout?: number) => setTimeout(scheduler, handler, timeout)) as any;
|
||||
globalThis.clearTimeout = (timeoutId: any) => {
|
||||
if (typeof timeoutId === 'object' && timeoutId && 'dispose' in timeoutId) {
|
||||
timeoutId.dispose();
|
||||
|
|
|
@ -38,7 +38,7 @@ body {
|
|||
}
|
||||
|
||||
.cpu {
|
||||
width: 45px;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.pid {
|
||||
|
|
|
@ -115,7 +115,7 @@ class ProcessHeaderTreeRenderer implements ITreeRenderer<ProcessInformation, voi
|
|||
}
|
||||
renderElement(node: ITreeNode<ProcessInformation, void>, index: number, templateData: IProcessItemTemplateData, height: number | undefined): void {
|
||||
templateData.name.textContent = localize('name', "Process Name");
|
||||
templateData.CPU.textContent = localize('cpu', "CPU %");
|
||||
templateData.CPU.textContent = localize('cpu', "CPU (%)");
|
||||
templateData.PID.textContent = localize('pid', "PID");
|
||||
templateData.memory.textContent = localize('memory', "Memory (MB)");
|
||||
|
||||
|
@ -181,12 +181,14 @@ class ProcessRenderer implements ITreeRenderer<ProcessItem, void, IProcessItemTe
|
|||
const windowTitle = this.mapPidToWindowTitle.get(element.pid);
|
||||
name = windowTitle !== undefined ? `${name} (${this.mapPidToWindowTitle.get(element.pid)})` : name;
|
||||
}
|
||||
const pid = element.pid.toFixed(0);
|
||||
|
||||
templateData.name.textContent = name;
|
||||
templateData.name.title = element.cmd;
|
||||
|
||||
templateData.CPU.textContent = element.load.toFixed(0);
|
||||
templateData.PID.textContent = element.pid.toFixed(0);
|
||||
templateData.PID.textContent = pid;
|
||||
templateData.PID.parentElement!.id = `pid-${pid}`;
|
||||
|
||||
const memory = this.platform === 'win32' ? element.mem : (this.totalMem * (element.mem / 100));
|
||||
templateData.memory.textContent = (memory / ByteSize.MB).toFixed(0);
|
||||
|
@ -450,7 +452,7 @@ class ProcessExplorer {
|
|||
items.push({
|
||||
label: localize('copy', "Copy"),
|
||||
click: () => {
|
||||
const row = document.getElementById(pid.toString());
|
||||
const row = document.getElementById(`pid-${pid}`);
|
||||
if (row) {
|
||||
this.nativeHostService.writeClipboardText(row.innerText);
|
||||
}
|
||||
|
|
|
@ -1518,31 +1518,6 @@ export namespace CoreNavigationCommands {
|
|||
precondition: undefined
|
||||
}));
|
||||
|
||||
export const ExpandLineSelection: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'expandLineSelection',
|
||||
precondition: undefined,
|
||||
kbOpts: {
|
||||
weight: CORE_WEIGHT,
|
||||
kbExpr: EditorContextKeys.textInputFocus,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KeyL
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
viewModel.model.pushStackElement();
|
||||
viewModel.setCursorStates(
|
||||
args.source,
|
||||
CursorChangeReason.Explicit,
|
||||
CursorMoveCommands.expandLineSelection(viewModel, viewModel.getCursorStates())
|
||||
);
|
||||
viewModel.revealPrimaryCursor(args.source, true);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export const CancelSelection: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand {
|
||||
constructor() {
|
||||
super({
|
||||
|
|
|
@ -172,8 +172,7 @@ export class TextAreaState {
|
|||
if (potentialEmojiInput !== null && potentialEmojiInput.length > 0) {
|
||||
// now we check that this is indeed an emoji
|
||||
// emojis can grow quite long, so a length check is of no help
|
||||
// allow-any-unicode-next-line
|
||||
// e.g. 1F3F4 E0067 E0062 E0065 E006E E0067 E007F ; fully-qualified # 🏴 England
|
||||
// e.g. 1F3F4 E0067 E0062 E0065 E006E E0067 E007F -- flag of England
|
||||
|
||||
// Oftentimes, emojis use Variation Selector-16 (U+FE0F), so that is a good hint
|
||||
// http://emojipedia.org/variation-selector-16/
|
||||
|
|
|
@ -25,9 +25,6 @@ export class DOMLineBreaksComputerFactory implements ILineBreaksComputerFactory
|
|||
}
|
||||
|
||||
public createLineBreaksComputer(fontInfo: FontInfo, tabSize: number, wrappingColumn: number, wrappingIndent: WrappingIndent): ILineBreaksComputer {
|
||||
tabSize = tabSize | 0; //@perf
|
||||
wrappingColumn = +wrappingColumn; //@perf
|
||||
|
||||
let requests: string[] = [];
|
||||
let injectedTexts: (LineInjectedText[] | null)[] = [];
|
||||
return {
|
||||
|
|
|
@ -331,13 +331,11 @@ export class ViewLine implements IVisibleLine {
|
|||
if (!this._renderedViewLine) {
|
||||
return null;
|
||||
}
|
||||
startColumn = startColumn | 0; // @perf
|
||||
endColumn = endColumn | 0; // @perf
|
||||
|
||||
startColumn = Math.min(this._renderedViewLine.input.lineContent.length + 1, Math.max(1, startColumn));
|
||||
endColumn = Math.min(this._renderedViewLine.input.lineContent.length + 1, Math.max(1, endColumn));
|
||||
|
||||
const stopRenderingLineAfter = this._renderedViewLine.input.stopRenderingLineAfter | 0; // @perf
|
||||
const stopRenderingLineAfter = this._renderedViewLine.input.stopRenderingLineAfter;
|
||||
let outsideRenderedLine = false;
|
||||
|
||||
if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter + 1 && endColumn > stopRenderingLineAfter + 1) {
|
||||
|
|
|
@ -2007,12 +2007,6 @@ class CodeEditorWidgetFocusTracker extends Disposable {
|
|||
this._hasFocus = false;
|
||||
this._onChange.fire(undefined);
|
||||
}));
|
||||
this._register(dom.addDisposableListener(domElement, 'focusin', () => {
|
||||
this._domFocusTracker.refreshState();
|
||||
}));
|
||||
this._register(dom.addDisposableListener(domElement, 'focusout', () => {
|
||||
this._domFocusTracker.refreshState();
|
||||
}));
|
||||
}
|
||||
|
||||
public hasFocus(): boolean {
|
||||
|
|
|
@ -640,6 +640,8 @@ export interface IEditorOptions {
|
|||
* Controls the behavior of editor guides.
|
||||
*/
|
||||
guides?: IGuidesOptions;
|
||||
|
||||
unicodeHighlight?: IUnicodeHighlightOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3245,6 +3247,120 @@ class EditorScrollbar extends BaseEditorOption<EditorOption.scrollbar, InternalE
|
|||
|
||||
//#endregion
|
||||
|
||||
//#region UnicodeHighlight
|
||||
|
||||
export type DeriveFromWorkspaceTrust = 'deriveFromWorkspaceTrust';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const deriveFromWorkspaceTrust: DeriveFromWorkspaceTrust = 'deriveFromWorkspaceTrust';
|
||||
|
||||
/**
|
||||
* Configuration options for unicode highlighting.
|
||||
*/
|
||||
export interface IUnicodeHighlightOptions {
|
||||
nonBasicASCII?: boolean | DeriveFromWorkspaceTrust;
|
||||
invisibleCharacters?: boolean | DeriveFromWorkspaceTrust;
|
||||
ambiguousCharacters?: boolean | DeriveFromWorkspaceTrust;
|
||||
includeComments?: boolean | DeriveFromWorkspaceTrust;
|
||||
/**
|
||||
* A list of allowed code points in a single string.
|
||||
*/
|
||||
allowedCharacters?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type InternalUnicodeHighlightOptions = Required<Readonly<IUnicodeHighlightOptions>>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const unicodeHighlightConfigKeys = {
|
||||
allowedCharacters: 'editor.unicodeHighlight.allowedCharacters',
|
||||
invisibleCharacters: 'editor.unicodeHighlight.invisibleCharacters',
|
||||
nonBasicASCII: 'editor.unicodeHighlight.nonBasicASCII',
|
||||
ambiguousCharacters: 'editor.unicodeHighlight.ambiguousCharacters',
|
||||
includeComments: 'editor.unicodeHighlight.includeComments',
|
||||
};
|
||||
|
||||
class UnicodeHighlight extends BaseEditorOption<EditorOption.unicodeHighlighting, InternalUnicodeHighlightOptions> {
|
||||
constructor() {
|
||||
const defaults: InternalUnicodeHighlightOptions = {
|
||||
nonBasicASCII: deriveFromWorkspaceTrust,
|
||||
invisibleCharacters: deriveFromWorkspaceTrust,
|
||||
ambiguousCharacters: deriveFromWorkspaceTrust,
|
||||
includeComments: deriveFromWorkspaceTrust,
|
||||
allowedCharacters: '',
|
||||
};
|
||||
|
||||
super(
|
||||
EditorOption.unicodeHighlighting, 'unicodeHighlight', defaults,
|
||||
{
|
||||
[unicodeHighlightConfigKeys.nonBasicASCII]: {
|
||||
restricted: true,
|
||||
type: ['boolean', 'string'],
|
||||
enum: [true, false, deriveFromWorkspaceTrust],
|
||||
default: defaults.nonBasicASCII,
|
||||
description: nls.localize('unicodeHighlight.nonBasicASCII', "Controls whether all non-basic ASCII characters are highlighted. Only characters between U+0020 and U+007E, tab, line-feed and carriage-return are considered basic ASCII.")
|
||||
},
|
||||
[unicodeHighlightConfigKeys.invisibleCharacters]: {
|
||||
restricted: true,
|
||||
type: ['boolean', 'string'],
|
||||
enum: [true, false, deriveFromWorkspaceTrust],
|
||||
default: defaults.invisibleCharacters,
|
||||
description: nls.localize('unicodeHighlight.invisibleCharacters', "Controls whether characters that just reserve space or have no width at all are highlighted.")
|
||||
},
|
||||
[unicodeHighlightConfigKeys.ambiguousCharacters]: {
|
||||
restricted: true,
|
||||
type: ['boolean', 'string'],
|
||||
enum: [true, false, deriveFromWorkspaceTrust],
|
||||
default: defaults.ambiguousCharacters,
|
||||
description: nls.localize('unicodeHighlight.ambiguousCharacters', "Controls whether characters are highlighted that can be confused with basic ASCII characters, except those that are common in the current user locale.")
|
||||
},
|
||||
[unicodeHighlightConfigKeys.includeComments]: {
|
||||
restricted: true,
|
||||
type: ['boolean', 'string'],
|
||||
enum: [true, false, deriveFromWorkspaceTrust],
|
||||
default: defaults.includeComments,
|
||||
description: nls.localize('unicodeHighlight.includeComments', "Controls whether characters in comments should also be subject to unicode highlighting.")
|
||||
},
|
||||
[unicodeHighlightConfigKeys.allowedCharacters]: {
|
||||
restricted: true,
|
||||
type: 'string',
|
||||
default: defaults.allowedCharacters,
|
||||
description: nls.localize('unicodeHighlight.allowedCharacters', "Defines allowed characters that are not being highlighted.")
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public validate(_input: any): InternalUnicodeHighlightOptions {
|
||||
if (!_input || typeof _input !== 'object') {
|
||||
return this.defaultValue;
|
||||
}
|
||||
const input = _input as IUnicodeHighlightOptions;
|
||||
return {
|
||||
nonBasicASCII: primitiveSet<boolean | DeriveFromWorkspaceTrust>(input.nonBasicASCII, deriveFromWorkspaceTrust, [true, false, deriveFromWorkspaceTrust]),
|
||||
invisibleCharacters: primitiveSet<boolean | DeriveFromWorkspaceTrust>(input.invisibleCharacters, deriveFromWorkspaceTrust, [true, false, deriveFromWorkspaceTrust]),
|
||||
ambiguousCharacters: primitiveSet<boolean | DeriveFromWorkspaceTrust>(input.ambiguousCharacters, deriveFromWorkspaceTrust, [true, false, deriveFromWorkspaceTrust]),
|
||||
includeComments: primitiveSet<boolean | DeriveFromWorkspaceTrust>(input.includeComments, deriveFromWorkspaceTrust, [true, false, deriveFromWorkspaceTrust]),
|
||||
allowedCharacters: string(input.allowedCharacters, ''),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function string(value: unknown, defaultValue: string): string {
|
||||
if (typeof value !== 'string') {
|
||||
return defaultValue;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region inlineSuggest
|
||||
|
||||
export interface IInlineSuggestOptions {
|
||||
|
@ -4219,6 +4335,7 @@ export const enum EditorOption {
|
|||
suggestSelection,
|
||||
tabCompletion,
|
||||
tabIndex,
|
||||
unicodeHighlighting,
|
||||
unusualLineTerminators,
|
||||
useShadowDOM,
|
||||
useTabStops,
|
||||
|
@ -4807,6 +4924,7 @@ export const EditorOptions = {
|
|||
EditorOption.tabIndex, 'tabIndex',
|
||||
0, -1, Constants.MAX_SAFE_SMALL_INTEGER
|
||||
)),
|
||||
unicodeHighlight: register(new UnicodeHighlight()),
|
||||
unusualLineTerminators: register(new EditorStringEnumOption(
|
||||
EditorOption.unusualLineTerminators, 'unusualLineTerminators',
|
||||
'prompt' as 'auto' | 'off' | 'prompt',
|
||||
|
|
|
@ -20,6 +20,7 @@ import { EnterAction, IndentAction, StandardAutoClosingPairConditional } from 'v
|
|||
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
import { IElectricAction } from 'vs/editor/common/modes/supports/electricCharacter';
|
||||
import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions';
|
||||
import { createScopedLineTokens } from 'vs/editor/common/modes/supports';
|
||||
|
||||
export class TypeOperations {
|
||||
|
||||
|
@ -502,89 +503,125 @@ export class TypeOperations {
|
|||
return !isBeforeStartingBrace && isBeforeClosingBrace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if typing `ch` at all `positions` in the `model` results in an
|
||||
* auto closing open sequence being typed.
|
||||
*
|
||||
* Auto closing open sequences can consist of multiple characters, which
|
||||
* can lead to ambiguities. In such a case, the longest auto-closing open
|
||||
* sequence is returned.
|
||||
*/
|
||||
private static _findAutoClosingPairOpen(config: CursorConfiguration, model: ITextModel, positions: Position[], ch: string): StandardAutoClosingPairConditional | null {
|
||||
const autoClosingPairCandidates = config.autoClosingPairs.autoClosingPairsOpenByEnd.get(ch);
|
||||
if (!autoClosingPairCandidates) {
|
||||
const candidates = config.autoClosingPairs.autoClosingPairsOpenByEnd.get(ch);
|
||||
if (!candidates) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Determine which auto-closing pair it is
|
||||
let autoClosingPair: StandardAutoClosingPairConditional | null = null;
|
||||
for (const autoClosingPairCandidate of autoClosingPairCandidates) {
|
||||
if (autoClosingPair === null || autoClosingPairCandidate.open.length > autoClosingPair.open.length) {
|
||||
let result: StandardAutoClosingPairConditional | null = null;
|
||||
for (const candidate of candidates) {
|
||||
if (result === null || candidate.open.length > result.open.length) {
|
||||
let candidateIsMatch = true;
|
||||
for (const position of positions) {
|
||||
const relevantText = model.getValueInRange(new Range(position.lineNumber, position.column - autoClosingPairCandidate.open.length + 1, position.lineNumber, position.column));
|
||||
if (relevantText + ch !== autoClosingPairCandidate.open) {
|
||||
const relevantText = model.getValueInRange(new Range(position.lineNumber, position.column - candidate.open.length + 1, position.lineNumber, position.column));
|
||||
if (relevantText + ch !== candidate.open) {
|
||||
candidateIsMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (candidateIsMatch) {
|
||||
autoClosingPair = autoClosingPairCandidate;
|
||||
result = candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
return autoClosingPair;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static _findSubAutoClosingPairClose(config: CursorConfiguration, autoClosingPair: StandardAutoClosingPairConditional): string {
|
||||
if (autoClosingPair.open.length <= 1) {
|
||||
return '';
|
||||
/**
|
||||
* Find another auto-closing pair that is contained by the one passed in.
|
||||
*
|
||||
* e.g. when having [(,)] and [(*,*)] as auto-closing pairs
|
||||
* this method will find [(,)] as a containment pair for [(*,*)]
|
||||
*/
|
||||
private static _findContainedAutoClosingPair(config: CursorConfiguration, pair: StandardAutoClosingPairConditional): StandardAutoClosingPairConditional | null {
|
||||
if (pair.open.length <= 1) {
|
||||
return null;
|
||||
}
|
||||
const lastChar = autoClosingPair.close.charAt(autoClosingPair.close.length - 1);
|
||||
const lastChar = pair.close.charAt(pair.close.length - 1);
|
||||
// get candidates with the same last character as close
|
||||
const subPairCandidates = config.autoClosingPairs.autoClosingPairsCloseByEnd.get(lastChar) || [];
|
||||
let subPairMatch: StandardAutoClosingPairConditional | null = null;
|
||||
for (const x of subPairCandidates) {
|
||||
if (x.open !== autoClosingPair.open && autoClosingPair.open.includes(x.open) && autoClosingPair.close.endsWith(x.close)) {
|
||||
if (!subPairMatch || x.open.length > subPairMatch.open.length) {
|
||||
subPairMatch = x;
|
||||
const candidates = config.autoClosingPairs.autoClosingPairsCloseByEnd.get(lastChar) || [];
|
||||
let result: StandardAutoClosingPairConditional | null = null;
|
||||
for (const candidate of candidates) {
|
||||
if (candidate.open !== pair.open && pair.open.includes(candidate.open) && pair.close.endsWith(candidate.close)) {
|
||||
if (!result || candidate.open.length > result.open.length) {
|
||||
result = candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (subPairMatch) {
|
||||
return subPairMatch.close;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static _getAutoClosingPairClose(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, insertOpenCharacter: boolean): string | null {
|
||||
private static _getAutoClosingPairClose(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, chIsAlreadyTyped: boolean): string | null {
|
||||
const chIsQuote = isQuote(ch);
|
||||
const autoCloseConfig = chIsQuote ? config.autoClosingQuotes : config.autoClosingBrackets;
|
||||
const autoCloseConfig = (chIsQuote ? config.autoClosingQuotes : config.autoClosingBrackets);
|
||||
const shouldAutoCloseBefore = (chIsQuote ? config.shouldAutoCloseBefore.quote : config.shouldAutoCloseBefore.bracket);
|
||||
|
||||
if (autoCloseConfig === 'never') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const autoClosingPair = this._findAutoClosingPairOpen(config, model, selections.map(s => s.getPosition()), ch);
|
||||
if (!autoClosingPair) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const subAutoClosingPairClose = this._findSubAutoClosingPairClose(config, autoClosingPair);
|
||||
let isSubAutoClosingPairPresent = true;
|
||||
|
||||
const shouldAutoCloseBefore = chIsQuote ? config.shouldAutoCloseBefore.quote : config.shouldAutoCloseBefore.bracket;
|
||||
|
||||
for (let i = 0, len = selections.length; i < len; i++) {
|
||||
const selection = selections[i];
|
||||
for (const selection of selections) {
|
||||
if (!selection.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const position = selection.getPosition();
|
||||
const lineText = model.getLineContent(position.lineNumber);
|
||||
const lineAfter = lineText.substring(position.column - 1);
|
||||
// This method is called both when typing (regularly) and when composition ends
|
||||
// This means that we need to work with a text buffer where sometimes `ch` is not
|
||||
// there (it is being typed right now) or with a text buffer where `ch` has already been typed
|
||||
//
|
||||
// In order to avoid adding checks for `chIsAlreadyTyped` in all places, we will work
|
||||
// with two conceptual positions, the position before `ch` and the position after `ch`
|
||||
//
|
||||
const positions: { lineNumber: number; beforeColumn: number; afterColumn: number; }[] = selections.map((s) => {
|
||||
const position = s.getPosition();
|
||||
if (chIsAlreadyTyped) {
|
||||
return { lineNumber: position.lineNumber, beforeColumn: position.column - ch.length, afterColumn: position.column };
|
||||
} else {
|
||||
return { lineNumber: position.lineNumber, beforeColumn: position.column, afterColumn: position.column };
|
||||
}
|
||||
});
|
||||
|
||||
if (!lineAfter.startsWith(subAutoClosingPairClose)) {
|
||||
isSubAutoClosingPairPresent = false;
|
||||
|
||||
// Find the longest auto-closing open pair in case of multiple ending in `ch`
|
||||
// e.g. when having [f","] and [","], it picks [f","] if the character before is f
|
||||
const pair = this._findAutoClosingPairOpen(config, model, positions.map(p => new Position(p.lineNumber, p.beforeColumn)), ch);
|
||||
if (!pair) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Sometimes, it is possible to have two auto-closing pairs that have a containment relationship
|
||||
// e.g. when having [(,)] and [(*,*)]
|
||||
// - when typing (, the resulting state is (|)
|
||||
// - when typing *, the desired resulting state is (*|*), not (*|*))
|
||||
const containedPair = this._findContainedAutoClosingPair(config, pair);
|
||||
const containedPairClose = containedPair ? containedPair.close : '';
|
||||
let isContainedPairPresent = true;
|
||||
|
||||
for (const position of positions) {
|
||||
const { lineNumber, beforeColumn, afterColumn } = position;
|
||||
const lineText = model.getLineContent(lineNumber);
|
||||
const lineBefore = lineText.substring(0, beforeColumn - 1);
|
||||
const lineAfter = lineText.substring(afterColumn - 1);
|
||||
|
||||
if (!lineAfter.startsWith(containedPairClose)) {
|
||||
isContainedPairPresent = false;
|
||||
}
|
||||
|
||||
// Only consider auto closing the pair if an allowed character follows or if another autoclosed pair closing brace follows
|
||||
if (lineText.length > position.column - 1) {
|
||||
const characterAfter = lineText.charAt(position.column - 1);
|
||||
if (lineAfter.length > 0) {
|
||||
const characterAfter = lineAfter.charAt(0);
|
||||
const isBeforeCloseBrace = TypeOperations._isBeforeClosingBrace(config, lineAfter);
|
||||
|
||||
if (!isBeforeCloseBrace && !shouldAutoCloseBefore(characterAfter)) {
|
||||
|
@ -592,49 +629,58 @@ export class TypeOperations {
|
|||
}
|
||||
}
|
||||
|
||||
if (!model.isCheapToTokenize(position.lineNumber)) {
|
||||
// Do not auto-close ' or " after a word character
|
||||
if (pair.open.length === 1 && (ch === '\'' || ch === '"') && autoCloseConfig !== 'always') {
|
||||
const wordSeparators = getMapForWordSeparators(config.wordSeparators);
|
||||
if (lineBefore.length > 0) {
|
||||
const characterBefore = lineBefore.charCodeAt(lineBefore.length - 1);
|
||||
if (wordSeparators.get(characterBefore) === WordCharacterClass.Regular) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!model.isCheapToTokenize(lineNumber)) {
|
||||
// Do not force tokenization
|
||||
return null;
|
||||
}
|
||||
|
||||
// Do not auto-close ' or " after a word character
|
||||
if (autoClosingPair.open.length === 1 && (ch === '\'' || ch === '"') && autoCloseConfig !== 'always') {
|
||||
const wordSeparators = getMapForWordSeparators(config.wordSeparators);
|
||||
if (insertOpenCharacter && position.column > 1 && wordSeparators.get(lineText.charCodeAt(position.column - 2)) === WordCharacterClass.Regular) {
|
||||
return null;
|
||||
}
|
||||
if (!insertOpenCharacter && position.column > 2 && wordSeparators.get(lineText.charCodeAt(position.column - 3)) === WordCharacterClass.Regular) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
model.forceTokenization(position.lineNumber);
|
||||
const lineTokens = model.getLineTokens(position.lineNumber);
|
||||
|
||||
let shouldAutoClosePair = false;
|
||||
try {
|
||||
shouldAutoClosePair = LanguageConfigurationRegistry.shouldAutoClosePair(autoClosingPair, lineTokens, insertOpenCharacter ? position.column : position.column - 1);
|
||||
} catch (e) {
|
||||
onUnexpectedError(e);
|
||||
}
|
||||
|
||||
if (!shouldAutoClosePair) {
|
||||
model.forceTokenization(lineNumber);
|
||||
const lineTokens = model.getLineTokens(lineNumber);
|
||||
const scopedLineTokens = createScopedLineTokens(lineTokens, beforeColumn - 1);
|
||||
if (!pair.shouldAutoClose(scopedLineTokens, beforeColumn - scopedLineTokens.firstCharOffset)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Typing for example a quote could either start a new string, in which case auto-closing is desirable
|
||||
// or it could end a previously started string, in which case auto-closing is not desirable
|
||||
//
|
||||
// In certain cases, it is really not possible to look at the previous token to determine
|
||||
// what would happen. That's why we do something really unusual, we pretend to type a different
|
||||
// character and ask the tokenizer what the outcome of doing that is: after typing a neutral
|
||||
// character, are we in a string (i.e. the quote would most likely end a string) or not?
|
||||
//
|
||||
const neutralCharacter = pair.findNeutralCharacter();
|
||||
if (neutralCharacter) {
|
||||
const tokenType = model.getTokenTypeIfInsertingCharacter(lineNumber, beforeColumn, neutralCharacter);
|
||||
if (!pair.isOK(tokenType)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isSubAutoClosingPairPresent) {
|
||||
return autoClosingPair.close.substring(0, autoClosingPair.close.length - subAutoClosingPairClose.length);
|
||||
if (isContainedPairPresent) {
|
||||
return pair.close.substring(0, pair.close.length - containedPairClose.length);
|
||||
} else {
|
||||
return autoClosingPair.close;
|
||||
return pair.close;
|
||||
}
|
||||
}
|
||||
|
||||
private static _runAutoClosingOpenCharType(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, insertOpenCharacter: boolean, autoClosingPairClose: string): EditOperationResult {
|
||||
private static _runAutoClosingOpenCharType(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, chIsAlreadyTyped: boolean, autoClosingPairClose: string): EditOperationResult {
|
||||
let commands: ICommand[] = [];
|
||||
for (let i = 0, len = selections.length; i < len; i++) {
|
||||
const selection = selections[i];
|
||||
commands[i] = new TypeWithAutoClosingCommand(selection, ch, insertOpenCharacter, autoClosingPairClose);
|
||||
commands[i] = new TypeWithAutoClosingCommand(selection, ch, !chIsAlreadyTyped, autoClosingPairClose);
|
||||
}
|
||||
return new EditOperationResult(EditOperationType.TypingOther, commands, {
|
||||
shouldPushStackElementBefore: true,
|
||||
|
@ -809,9 +855,9 @@ export class TypeOperations {
|
|||
});
|
||||
}
|
||||
|
||||
const autoClosingPairClose = this._getAutoClosingPairClose(config, model, selections, ch, false);
|
||||
const autoClosingPairClose = this._getAutoClosingPairClose(config, model, selections, ch, true);
|
||||
if (autoClosingPairClose !== null) {
|
||||
return this._runAutoClosingOpenCharType(prevEditOperationType, config, model, selections, ch, false, autoClosingPairClose);
|
||||
return this._runAutoClosingOpenCharType(prevEditOperationType, config, model, selections, ch, true, autoClosingPairClose);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -853,9 +899,9 @@ export class TypeOperations {
|
|||
}
|
||||
|
||||
if (!isDoingComposition) {
|
||||
const autoClosingPairClose = this._getAutoClosingPairClose(config, model, selections, ch, true);
|
||||
const autoClosingPairClose = this._getAutoClosingPairClose(config, model, selections, ch, false);
|
||||
if (autoClosingPairClose) {
|
||||
return this._runAutoClosingOpenCharType(prevEditOperationType, config, model, selections, ch, true, autoClosingPairClose);
|
||||
return this._runAutoClosingOpenCharType(prevEditOperationType, config, model, selections, ch, false, autoClosingPairClose);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -100,6 +100,23 @@ export class Range {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if `position` is in `range`. If the position is at the edges, will return false.
|
||||
* @internal
|
||||
*/
|
||||
public static strictContainsPosition(range: IRange, position: IPosition): boolean {
|
||||
if (position.lineNumber < range.startLineNumber || position.lineNumber > range.endLineNumber) {
|
||||
return false;
|
||||
}
|
||||
if (position.lineNumber === range.startLineNumber && position.column <= range.startColumn) {
|
||||
return false;
|
||||
}
|
||||
if (position.lineNumber === range.endLineNumber && position.column >= range.endColumn) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if range is in this range. If the range is equal to this range, will return true.
|
||||
*/
|
||||
|
|
|
@ -13,7 +13,7 @@ export class Token {
|
|||
public readonly language: string;
|
||||
|
||||
constructor(offset: number, type: string, language: string) {
|
||||
this.offset = offset | 0;// @perf
|
||||
this.offset = offset;
|
||||
this.type = type;
|
||||
this.language = language;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import { IRange, Range } from 'vs/editor/common/core/range';
|
|||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { IModelContentChange, IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent, IModelTokensChangedEvent, ModelInjectedTextChangedEvent, ModelRawContentChangedEvent } from 'vs/editor/common/model/textModelEvents';
|
||||
import { SearchData } from 'vs/editor/common/model/textModelSearch';
|
||||
import { FormattingOptions } from 'vs/editor/common/modes';
|
||||
import { FormattingOptions, StandardTokenType } from 'vs/editor/common/modes';
|
||||
import { ThemeColor } from 'vs/platform/theme/common/themeService';
|
||||
import { MultilineTokens, MultilineTokens2 } from 'vs/editor/common/model/tokensStore';
|
||||
import { TextChange } from 'vs/editor/common/model/textChange';
|
||||
|
@ -168,6 +168,12 @@ export interface IModelDecorationOptions {
|
|||
* If set, text will be injected in the view before the range.
|
||||
*/
|
||||
before?: InjectedTextOptions | null;
|
||||
|
||||
/**
|
||||
* If set, this decoration will not be rendered for comment tokens.
|
||||
* @internal
|
||||
*/
|
||||
hideInCommentTokens?: boolean | null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -944,6 +950,13 @@ export interface ITextModel {
|
|||
*/
|
||||
getLanguageIdAtPosition(lineNumber: number, column: number): string;
|
||||
|
||||
/**
|
||||
* Returns the standard token type for a character if the character were to be inserted at
|
||||
* the given position. If the result cannot be accurate, it returns null.
|
||||
* @internal
|
||||
*/
|
||||
getTokenTypeIfInsertingCharacter(lineNumber: number, column: number, character: string): StandardTokenType;
|
||||
|
||||
/**
|
||||
* Get the word under or besides `position`.
|
||||
* @param position The position to look for a word.
|
||||
|
|
|
@ -24,7 +24,7 @@ import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguag
|
|||
import { SearchData, SearchParams, TextModelSearch } from 'vs/editor/common/model/textModelSearch';
|
||||
import { TextModelTokenization } from 'vs/editor/common/model/textModelTokens';
|
||||
import { getWordAtText } from 'vs/editor/common/model/wordHelper';
|
||||
import { FormattingOptions } from 'vs/editor/common/modes';
|
||||
import { FormattingOptions, StandardTokenType } from 'vs/editor/common/modes';
|
||||
import { ILanguageConfigurationService, ResolvedLanguageConfiguration } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
import { NULL_MODE_ID } from 'vs/editor/common/modes/nullMode';
|
||||
import { ThemeColor } from 'vs/platform/theme/common/themeService';
|
||||
|
@ -2126,6 +2126,11 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
|
|||
return lineTokens.getLanguageId(lineTokens.findTokenIndexAtOffset(position.column - 1));
|
||||
}
|
||||
|
||||
public getTokenTypeIfInsertingCharacter(lineNumber: number, column: number, character: string): StandardTokenType {
|
||||
const position = this.validatePosition(new Position(lineNumber, column));
|
||||
return this._tokenization.getTokenTypeIfInsertingCharacter(position, character);
|
||||
}
|
||||
|
||||
private getLanguageConfiguration(languageId: string): ResolvedLanguageConfiguration {
|
||||
return this._languageConfigurationService.getLanguageConfiguration(languageId);
|
||||
}
|
||||
|
@ -2452,7 +2457,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
|
|||
const bracketsContainingActivePosition =
|
||||
(startLineNumber <= activePosition.lineNumber && activePosition.lineNumber <= endLineNumber)
|
||||
// Does active position intersect with the view port? -> Intersect bracket pairs with activePosition
|
||||
? bracketPairs.filter(bp => bp.range.containsPosition(activePosition))
|
||||
? bracketPairs.filter(bp => Range.strictContainsPosition(bp.range, activePosition))
|
||||
: this._bracketPairColorizer.getBracketPairsInRange(
|
||||
Range.fromPositions(activePosition)
|
||||
);
|
||||
|
@ -3039,6 +3044,8 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions {
|
|||
readonly afterContentClassName: string | null;
|
||||
readonly after: ModelDecorationInjectedTextOptions | null;
|
||||
readonly before: ModelDecorationInjectedTextOptions | null;
|
||||
readonly hideInCommentTokens: boolean | null;
|
||||
|
||||
|
||||
private constructor(options: model.IModelDecorationOptions) {
|
||||
this.description = options.description;
|
||||
|
@ -3062,6 +3069,7 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions {
|
|||
this.afterContentClassName = options.afterContentClassName ? cleanClassName(options.afterContentClassName) : null;
|
||||
this.after = options.after ? ModelDecorationInjectedTextOptions.from(options.after) : null;
|
||||
this.before = options.before ? ModelDecorationInjectedTextOptions.from(options.before) : null;
|
||||
this.hideInCommentTokens = options.hideInCommentTokens ?? false;
|
||||
}
|
||||
}
|
||||
ModelDecorationOptions.EMPTY = ModelDecorationOptions.register({ description: 'empty' });
|
||||
|
|
|
@ -9,13 +9,13 @@ import { LineTokens } from 'vs/editor/common/core/lineTokens';
|
|||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { TokenizationResult2 } from 'vs/editor/common/core/token';
|
||||
import { ILanguageIdCodec, IState, ITokenizationSupport, TokenizationRegistry } from 'vs/editor/common/modes';
|
||||
import { ILanguageIdCodec, IState, ITokenizationSupport, StandardTokenType, TokenizationRegistry } from 'vs/editor/common/modes';
|
||||
import { nullTokenize2 } from 'vs/editor/common/modes/nullMode';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
import { MultilineTokensBuilder, countEOL } from 'vs/editor/common/model/tokensStore';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { runWhenIdle, IdleDeadline } from 'vs/base/common/async';
|
||||
|
||||
const enum Constants {
|
||||
CHEAP_TOKENIZATION_LENGTH_LIMIT = 2048
|
||||
|
@ -255,19 +255,26 @@ export class TextModelTokenization extends Disposable {
|
|||
this._beginBackgroundTokenization();
|
||||
}
|
||||
|
||||
private _isScheduled = false;
|
||||
private _beginBackgroundTokenization(): void {
|
||||
if (this._textModel.isAttachedToEditor() && this._hasLinesToTokenize()) {
|
||||
platform.setImmediate(() => {
|
||||
if (this._isDisposed) {
|
||||
// disposed in the meantime
|
||||
return;
|
||||
}
|
||||
this._revalidateTokensNow();
|
||||
});
|
||||
if (this._isScheduled || !this._textModel.isAttachedToEditor() || !this._hasLinesToTokenize()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isScheduled = true;
|
||||
runWhenIdle((deadline) => {
|
||||
this._isScheduled = false;
|
||||
|
||||
if (this._isDisposed) {
|
||||
// disposed in the meantime
|
||||
return;
|
||||
}
|
||||
|
||||
this._revalidateTokensNow(deadline);
|
||||
});
|
||||
}
|
||||
|
||||
private _revalidateTokensNow(): void {
|
||||
private _revalidateTokensNow(deadline: IdleDeadline): void {
|
||||
const textModelLastLineNumber = this._textModel.getLineCount();
|
||||
|
||||
const MAX_ALLOWED_TIME = 1;
|
||||
|
@ -275,7 +282,7 @@ export class TextModelTokenization extends Disposable {
|
|||
const sw = StopWatch.create(false);
|
||||
let tokenizedLineNumber = -1;
|
||||
|
||||
while (this._hasLinesToTokenize()) {
|
||||
do {
|
||||
if (sw.elapsed() > MAX_ALLOWED_TIME) {
|
||||
// Stop if MAX_ALLOWED_TIME is reached
|
||||
break;
|
||||
|
@ -286,7 +293,7 @@ export class TextModelTokenization extends Disposable {
|
|||
if (tokenizedLineNumber >= textModelLastLineNumber) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (this._hasLinesToTokenize() && deadline.timeRemaining() > 0);
|
||||
|
||||
this._beginBackgroundTokenization();
|
||||
this._textModel.setTokens(builder.tokens, !this._hasLinesToTokenize());
|
||||
|
@ -309,6 +316,37 @@ export class TextModelTokenization extends Disposable {
|
|||
this._textModel.setTokens(builder.tokens, !this._hasLinesToTokenize());
|
||||
}
|
||||
|
||||
public getTokenTypeIfInsertingCharacter(position: Position, character: string): StandardTokenType {
|
||||
if (!this._tokenizationSupport) {
|
||||
return StandardTokenType.Other;
|
||||
}
|
||||
|
||||
this.forceTokenization(position.lineNumber);
|
||||
const lineStartState = this._tokenizationStateStore.getBeginState(position.lineNumber - 1);
|
||||
if (!lineStartState) {
|
||||
return StandardTokenType.Other;
|
||||
}
|
||||
|
||||
const languageId = this._textModel.getLanguageId();
|
||||
const lineContent = this._textModel.getLineContent(position.lineNumber);
|
||||
|
||||
// Create the text as if `character` was inserted
|
||||
const text = (
|
||||
lineContent.substring(0, position.column - 1)
|
||||
+ character
|
||||
+ lineContent.substring(position.column - 1)
|
||||
);
|
||||
|
||||
const r = safeTokenize(this._languageIdCodec, languageId, this._tokenizationSupport, text, true, lineStartState);
|
||||
const lineTokens = new LineTokens(r.tokens, text, this._languageIdCodec);
|
||||
if (lineTokens.getCount() === 0) {
|
||||
return StandardTokenType.Other;
|
||||
}
|
||||
|
||||
const tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
|
||||
return lineTokens.getStandardTokenType(tokenIndex);
|
||||
}
|
||||
|
||||
public isCheapToTokenize(lineNumber: number): boolean {
|
||||
if (!this._tokenizationSupport) {
|
||||
return true;
|
||||
|
|
|
@ -677,6 +677,8 @@ export interface InlineCompletionContext {
|
|||
export interface SelectedSuggestionInfo {
|
||||
range: IRange;
|
||||
text: string;
|
||||
isSnippetText: boolean;
|
||||
completionKind: CompletionItemKind;
|
||||
}
|
||||
|
||||
export interface InlineCompletion {
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import { StandardTokenType } from 'vs/editor/common/modes';
|
||||
import { ScopedLineTokens } from 'vs/editor/common/modes/supports';
|
||||
|
||||
/**
|
||||
* Describes how comments for a language work.
|
||||
|
@ -268,11 +270,12 @@ export interface CompleteEnterAction {
|
|||
* @internal
|
||||
*/
|
||||
export class StandardAutoClosingPairConditional {
|
||||
_standardAutoClosingPairConditionalBrand: void = undefined;
|
||||
|
||||
readonly open: string;
|
||||
readonly close: string;
|
||||
private readonly _standardTokenMask: number;
|
||||
private _neutralCharacter: string | null = null;
|
||||
private _neutralCharacterSearched: boolean = false;
|
||||
|
||||
constructor(source: IAutoClosingPairConditional) {
|
||||
this.open = source.open;
|
||||
|
@ -302,6 +305,46 @@ export class StandardAutoClosingPairConditional {
|
|||
public isOK(standardToken: StandardTokenType): boolean {
|
||||
return (this._standardTokenMask & <number>standardToken) === 0;
|
||||
}
|
||||
|
||||
public shouldAutoClose(context: ScopedLineTokens, column: number): boolean {
|
||||
// Always complete on empty line
|
||||
if (context.getTokenCount() === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const tokenIndex = context.findTokenIndexAtOffset(column - 2);
|
||||
const standardTokenType = context.getStandardTokenType(tokenIndex);
|
||||
return this.isOK(standardTokenType);
|
||||
}
|
||||
|
||||
private _findNeutralCharacterInRange(fromCharCode: number, toCharCode: number): string | null {
|
||||
for (let charCode = fromCharCode; charCode <= toCharCode; charCode++) {
|
||||
const character = String.fromCharCode(charCode);
|
||||
if (!this.open.includes(character) && !this.close.includes(character)) {
|
||||
return character;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a character in the range [0-9a-zA-Z] that does not appear in the open or close
|
||||
*/
|
||||
public findNeutralCharacter(): string | null {
|
||||
if (!this._neutralCharacterSearched) {
|
||||
this._neutralCharacterSearched = true;
|
||||
if (!this._neutralCharacter) {
|
||||
this._neutralCharacter = this._findNeutralCharacterInRange(CharCode.Digit0, CharCode.Digit9);
|
||||
}
|
||||
if (!this._neutralCharacter) {
|
||||
this._neutralCharacter = this._findNeutralCharacterInRange(CharCode.a, CharCode.z);
|
||||
}
|
||||
if (!this._neutralCharacter) {
|
||||
this._neutralCharacter = this._findNeutralCharacterInRange(CharCode.A, CharCode.Z);
|
||||
}
|
||||
}
|
||||
return this._neutralCharacter;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -10,7 +10,7 @@ import { LineTokens } from 'vs/editor/common/core/lineTokens';
|
|||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { DEFAULT_WORD_REGEXP, ensureValidWordDefinition } from 'vs/editor/common/model/wordHelper';
|
||||
import { EnterAction, FoldingRules, IAutoClosingPair, IndentAction, IndentationRule, LanguageConfiguration, StandardAutoClosingPairConditional, CompleteEnterAction, AutoClosingPairs, CharacterPair, ExplicitLanguageConfiguration } from 'vs/editor/common/modes/languageConfiguration';
|
||||
import { EnterAction, FoldingRules, IAutoClosingPair, IndentAction, IndentationRule, LanguageConfiguration, CompleteEnterAction, AutoClosingPairs, CharacterPair, ExplicitLanguageConfiguration } from 'vs/editor/common/modes/languageConfiguration';
|
||||
import { createScopedLineTokens, ScopedLineTokens } from 'vs/editor/common/modes/supports';
|
||||
import { CharacterPairSupport } from 'vs/editor/common/modes/supports/characterPair';
|
||||
import { BracketElectricCharacterSupport, IElectricAction } from 'vs/editor/common/modes/supports/electricCharacter';
|
||||
|
@ -280,11 +280,6 @@ export class LanguageConfigurationRegistryImpl {
|
|||
return characterPairSupport.getSurroundingPairs();
|
||||
}
|
||||
|
||||
public shouldAutoClosePair(autoClosingPair: StandardAutoClosingPairConditional, context: LineTokens, column: number): boolean {
|
||||
const scopedLineTokens = createScopedLineTokens(context, column - 1);
|
||||
return CharacterPairSupport.shouldAutoClosePair(autoClosingPair, scopedLineTokens, column - scopedLineTokens.firstCharOffset);
|
||||
}
|
||||
|
||||
// end characterPair
|
||||
|
||||
public getWordDefinition(languageId: string): RegExp {
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IAutoClosingPair, StandardAutoClosingPairConditional, LanguageConfiguration, CharacterPair } from 'vs/editor/common/modes/languageConfiguration';
|
||||
import { ScopedLineTokens } from 'vs/editor/common/modes/supports';
|
||||
|
||||
export class CharacterPairSupport {
|
||||
|
||||
|
@ -58,17 +57,6 @@ export class CharacterPairSupport {
|
|||
return this._autoCloseBefore;
|
||||
}
|
||||
|
||||
public static shouldAutoClosePair(autoClosingPair: StandardAutoClosingPairConditional, context: ScopedLineTokens, column: number): boolean {
|
||||
// Always complete on empty line
|
||||
if (context.getTokenCount() === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const tokenIndex = context.findTokenIndexAtOffset(column - 2);
|
||||
const standardTokenType = context.getStandardTokenType(tokenIndex);
|
||||
return autoClosingPair.isOK(standardTokenType);
|
||||
}
|
||||
|
||||
public getSurroundingPairs(): IAutoClosingPair[] {
|
||||
return this._surroundingPairs;
|
||||
}
|
||||
|
|
188
src/vs/editor/common/modes/unicodeTextModelHighlighter.ts
Normal file
188
src/vs/editor/common/modes/unicodeTextModelHighlighter.ts
Normal file
|
@ -0,0 +1,188 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { Searcher } from 'vs/editor/common/model/textModelSearch';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
|
||||
export class UnicodeTextModelHighlighter {
|
||||
public static computeUnicodeHighlights(model: IUnicodeCharacterSearcherTarget, options: UnicodeHighlighterOptions, range?: IRange): Range[] {
|
||||
const startLine = range ? range.startLineNumber : 1;
|
||||
const endLine = range ? range.endLineNumber : model.getLineCount();
|
||||
|
||||
const codePointHighlighter = new CodePointHighlighter(options);
|
||||
|
||||
const candidates = codePointHighlighter.getCandidateCodePoints();
|
||||
let regex: RegExp;
|
||||
if (candidates === 'allNonBasicAscii') {
|
||||
regex = new RegExp('[^\\t\\n\\r\\x20-\\x7E]', 'g');
|
||||
} else {
|
||||
regex = new RegExp(`${buildRegExpCharClassExpr(Array.from(candidates))}`, 'g');
|
||||
}
|
||||
|
||||
const searcher = new Searcher(null, regex);
|
||||
const result: Range[] = [];
|
||||
let m: RegExpExecArray | null;
|
||||
for (let lineNumber = startLine, lineCount = endLine; lineNumber <= lineCount; lineNumber++) {
|
||||
const lineContent = model.getLineContent(lineNumber);
|
||||
const lineLength = lineContent.length;
|
||||
|
||||
// Reset regex to search from the beginning
|
||||
searcher.reset(0);
|
||||
do {
|
||||
m = searcher.next(lineContent);
|
||||
if (m) {
|
||||
let startIndex = m.index;
|
||||
let endIndex = m.index + m[0].length;
|
||||
|
||||
// Extend range to entire code point
|
||||
if (startIndex > 0) {
|
||||
const charCodeBefore = lineContent.charCodeAt(startIndex - 1);
|
||||
if (strings.isHighSurrogate(charCodeBefore)) {
|
||||
startIndex--;
|
||||
}
|
||||
}
|
||||
if (endIndex + 1 < lineLength) {
|
||||
const charCodeBefore = lineContent.charCodeAt(endIndex - 1);
|
||||
if (strings.isHighSurrogate(charCodeBefore)) {
|
||||
endIndex++;
|
||||
}
|
||||
}
|
||||
const str = lineContent.substring(startIndex, endIndex);
|
||||
if (codePointHighlighter.shouldHighlightNonBasicASCII(str) !== SimpleHighlightReason.None) {
|
||||
result.push(new Range(lineNumber, startIndex + 1, lineNumber, endIndex + 1));
|
||||
|
||||
const maxResultLength = 1000;
|
||||
if (result.length > maxResultLength) {
|
||||
// TODO@hediet a message should be shown in this case
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (m);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static computeUnicodeHighlightReason(char: string, options: UnicodeHighlighterOptions): UnicodeHighlighterReason | null {
|
||||
const codePointHighlighter = new CodePointHighlighter(options);
|
||||
|
||||
const reason = codePointHighlighter.shouldHighlightNonBasicASCII(char);
|
||||
switch (reason) {
|
||||
case SimpleHighlightReason.None:
|
||||
return null;
|
||||
case SimpleHighlightReason.Invisible:
|
||||
return { kind: UnicodeHighlighterReasonKind.Invisible };
|
||||
|
||||
case SimpleHighlightReason.Ambiguous:
|
||||
const primaryConfusable = strings.AmbiguousCharacters.getPrimaryConfusable(char.codePointAt(0)!)!;
|
||||
return { kind: UnicodeHighlighterReasonKind.Ambiguous, confusableWith: String.fromCodePoint(primaryConfusable) };
|
||||
|
||||
case SimpleHighlightReason.NonBasicASCII:
|
||||
return { kind: UnicodeHighlighterReasonKind.NonBasicAscii };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildRegExpCharClassExpr(codePoints: number[], flags?: string): string {
|
||||
const src = `[${strings.escapeRegExpCharacters(
|
||||
codePoints.map((i) => String.fromCodePoint(i)).join('')
|
||||
)}]`;
|
||||
return src;
|
||||
}
|
||||
|
||||
export const enum UnicodeHighlighterReasonKind {
|
||||
Ambiguous, Invisible, NonBasicAscii
|
||||
}
|
||||
|
||||
export type UnicodeHighlighterReason = {
|
||||
kind: UnicodeHighlighterReasonKind.Ambiguous;
|
||||
confusableWith: string;
|
||||
} | {
|
||||
kind: UnicodeHighlighterReasonKind.Invisible;
|
||||
} | {
|
||||
kind: UnicodeHighlighterReasonKind.NonBasicAscii
|
||||
};
|
||||
|
||||
class CodePointHighlighter {
|
||||
private readonly allowedCodePoints: Set<number>;
|
||||
constructor(private readonly options: UnicodeHighlighterOptions) {
|
||||
this.allowedCodePoints = new Set(options.allowedCodePoints);
|
||||
}
|
||||
|
||||
public getCandidateCodePoints(): Set<number> | 'allNonBasicAscii' {
|
||||
if (this.options.nonBasicASCII) {
|
||||
return 'allNonBasicAscii';
|
||||
}
|
||||
|
||||
const set = new Set<number>();
|
||||
|
||||
if (this.options.invisibleCharacters) {
|
||||
for (const cp of strings.InvisibleCharacters.codePoints) {
|
||||
set.add(cp);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.ambiguousCharacters) {
|
||||
for (const cp of strings.AmbiguousCharacters.getPrimaryConfusableCodePoints()) {
|
||||
set.add(cp);
|
||||
}
|
||||
}
|
||||
|
||||
for (const cp of this.allowedCodePoints) {
|
||||
set.delete(cp);
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
public shouldHighlightNonBasicASCII(character: string): SimpleHighlightReason {
|
||||
const codePoint = character.codePointAt(0)!;
|
||||
|
||||
if (this.allowedCodePoints.has(codePoint)) {
|
||||
return SimpleHighlightReason.None;
|
||||
}
|
||||
|
||||
if (this.options.nonBasicASCII) {
|
||||
return SimpleHighlightReason.NonBasicASCII;
|
||||
}
|
||||
|
||||
if (this.options.invisibleCharacters) {
|
||||
const isAllowedInvisibleCharacter = character === ' ' || character === '\n' || character === '\t';
|
||||
// TODO check for emojis
|
||||
if (!isAllowedInvisibleCharacter && strings.InvisibleCharacters.isInvisibleCharacter(codePoint)) {
|
||||
return SimpleHighlightReason.Invisible;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.ambiguousCharacters) {
|
||||
if (strings.AmbiguousCharacters.isAmbiguous(codePoint)) {
|
||||
return SimpleHighlightReason.Ambiguous;
|
||||
}
|
||||
}
|
||||
|
||||
return SimpleHighlightReason.None;
|
||||
}
|
||||
}
|
||||
|
||||
const enum SimpleHighlightReason {
|
||||
None,
|
||||
NonBasicASCII,
|
||||
Invisible,
|
||||
Ambiguous
|
||||
}
|
||||
|
||||
export interface IUnicodeCharacterSearcherTarget {
|
||||
getLineCount(): number;
|
||||
getLineContent(lineNumber: number): string;
|
||||
}
|
||||
|
||||
export interface UnicodeHighlighterOptions {
|
||||
nonBasicASCII: boolean;
|
||||
ambiguousCharacters: boolean;
|
||||
invisibleCharacters: boolean;
|
||||
includeComments: boolean;
|
||||
allowedCodePoints: number[];
|
||||
}
|
|
@ -23,6 +23,7 @@ import { createMonacoBaseAPI } from 'vs/editor/common/standalone/standaloneBase'
|
|||
import * as types from 'vs/base/common/types';
|
||||
import { EditorWorkerHost } from 'vs/editor/common/services/editorWorkerServiceImpl';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
import { UnicodeTextModelHighlighter, UnicodeHighlighterOptions } from 'vs/editor/common/modes/unicodeTextModelHighlighter';
|
||||
|
||||
export interface IMirrorModel extends IMirrorTextModel {
|
||||
readonly uri: URI;
|
||||
|
@ -371,6 +372,14 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
|
|||
delete this._models[strURL];
|
||||
}
|
||||
|
||||
public async computeUnicodeHighlights(url: string, options: UnicodeHighlighterOptions, range?: IRange): Promise<IRange[]> {
|
||||
const model = this._getModel(url);
|
||||
if (!model) {
|
||||
return [];
|
||||
}
|
||||
return UnicodeTextModelHighlighter.computeUnicodeHighlights(model, options, range);
|
||||
}
|
||||
|
||||
// ---- BEGIN diff --------------------------------------------------------------------------
|
||||
|
||||
public async computeDiff(originalUrl: string, modifiedUrl: string, ignoreTrimWhitespace: boolean, maxComputationTime: number): Promise<IDiffComputationResult | null> {
|
||||
|
|
|
@ -7,6 +7,7 @@ import { URI } from 'vs/base/common/uri';
|
|||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { IChange, ILineChange } from 'vs/editor/common/editorCommon';
|
||||
import { IInplaceReplaceSupportResult, TextEdit } from 'vs/editor/common/modes';
|
||||
import { UnicodeHighlighterOptions } from 'vs/editor/common/modes/unicodeTextModelHighlighter';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export const ID_EDITOR_WORKER_SERVICE = 'editorWorkerService';
|
||||
|
@ -21,6 +22,9 @@ export interface IDiffComputationResult {
|
|||
export interface IEditorWorkerService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
canComputeUnicodeHighlights(uri: URI): boolean;
|
||||
computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise<IRange[]>;
|
||||
|
||||
computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean, maxComputationTime: number): Promise<IDiffComputationResult | null>;
|
||||
|
||||
canComputeDirtyDiff(original: URI, modified: URI): boolean;
|
||||
|
|
|
@ -23,6 +23,7 @@ import { isNonEmptyArray } from 'vs/base/common/arrays';
|
|||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
import { canceled } from 'vs/base/common/errors';
|
||||
import { UnicodeHighlighterOptions } from 'vs/editor/common/modes/unicodeTextModelHighlighter';
|
||||
|
||||
/**
|
||||
* Stop syncing a model to the worker if it was not needed for 1 min.
|
||||
|
@ -81,6 +82,14 @@ export class EditorWorkerServiceImpl extends Disposable implements IEditorWorker
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
public canComputeUnicodeHighlights(uri: URI): boolean {
|
||||
return canSyncModel(this._modelService, uri);
|
||||
}
|
||||
|
||||
public computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise<IRange[]> {
|
||||
return this._workerManager.withWorker().then(client => client.computedUnicodeHighlights(uri, options, range));
|
||||
}
|
||||
|
||||
public computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean, maxComputationTime: number): Promise<IDiffComputationResult | null> {
|
||||
return this._workerManager.withWorker().then(client => client.computeDiff(original, modified, ignoreTrimWhitespace, maxComputationTime));
|
||||
}
|
||||
|
@ -466,6 +475,12 @@ export class EditorWorkerClient extends Disposable implements IEditorWorkerClien
|
|||
});
|
||||
}
|
||||
|
||||
public computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise<IRange[]> {
|
||||
return this._withSyncedResources([uri]).then(proxy => {
|
||||
return proxy.computeUnicodeHighlights(uri.toString(), options, range);
|
||||
});
|
||||
}
|
||||
|
||||
public computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean, maxComputationTime: number): Promise<IDiffComputationResult | null> {
|
||||
return this._withSyncedResources([original, modified], /* forceLargeModels */true).then(proxy => {
|
||||
return proxy.computeDiff(original.toString(), modified.toString(), ignoreTrimWhitespace, maxComputationTime);
|
||||
|
|
|
@ -279,25 +279,26 @@ export enum EditorOption {
|
|||
suggestSelection = 109,
|
||||
tabCompletion = 110,
|
||||
tabIndex = 111,
|
||||
unusualLineTerminators = 112,
|
||||
useShadowDOM = 113,
|
||||
useTabStops = 114,
|
||||
wordSeparators = 115,
|
||||
wordWrap = 116,
|
||||
wordWrapBreakAfterCharacters = 117,
|
||||
wordWrapBreakBeforeCharacters = 118,
|
||||
wordWrapColumn = 119,
|
||||
wordWrapOverride1 = 120,
|
||||
wordWrapOverride2 = 121,
|
||||
wrappingIndent = 122,
|
||||
wrappingStrategy = 123,
|
||||
showDeprecated = 124,
|
||||
inlayHints = 125,
|
||||
editorClassName = 126,
|
||||
pixelRatio = 127,
|
||||
tabFocusMode = 128,
|
||||
layoutInfo = 129,
|
||||
wrappingInfo = 130
|
||||
unicodeHighlighting = 112,
|
||||
unusualLineTerminators = 113,
|
||||
useShadowDOM = 114,
|
||||
useTabStops = 115,
|
||||
wordSeparators = 116,
|
||||
wordWrap = 117,
|
||||
wordWrapBreakAfterCharacters = 118,
|
||||
wordWrapBreakBeforeCharacters = 119,
|
||||
wordWrapColumn = 120,
|
||||
wordWrapOverride1 = 121,
|
||||
wordWrapOverride2 = 122,
|
||||
wrappingIndent = 123,
|
||||
wrappingStrategy = 124,
|
||||
showDeprecated = 125,
|
||||
inlayHints = 126,
|
||||
editorClassName = 127,
|
||||
pixelRatio = 128,
|
||||
tabFocusMode = 129,
|
||||
layoutInfo = 130,
|
||||
wrappingInfo = 131
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -76,6 +76,8 @@ export const editorBracketPairGuideActiveBackground4 = registerColor('editorBrac
|
|||
export const editorBracketPairGuideActiveBackground5 = registerColor('editorBracketPairGuide.activeBackground5', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.activeBackground5', 'Background color of active bracket pair guides (5). Requires enabling bracket pair guides.'));
|
||||
export const editorBracketPairGuideActiveBackground6 = registerColor('editorBracketPairGuide.activeBackground6', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.activeBackground6', 'Background color of active bracket pair guides (6). Requires enabling bracket pair guides.'));
|
||||
|
||||
export const editorUnicodeHighlightBorder = registerColor('editorUnicodeHighlight.border', { dark: '#ff0000', light: '#ff0000', hc: '#ff0000' }, nls.localize('editorUnicodeHighlight.border', 'Border color used to highlight unicode characters.'));
|
||||
|
||||
|
||||
// contains all color rules that used to defined in editor/browser/widget/editor.css
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
|
|
|
@ -175,9 +175,9 @@ export class OverviewZoneManager {
|
|||
|
||||
public resolveColorZones(): ColorZone[] {
|
||||
const colorZonesInvalid = this._colorZonesInvalid;
|
||||
const lineHeight = Math.floor(this._lineHeight); // @perf
|
||||
const totalHeight = Math.floor(this.getCanvasHeight()); // @perf
|
||||
const outerHeight = Math.floor(this._outerHeight); // @perf
|
||||
const lineHeight = Math.floor(this._lineHeight);
|
||||
const totalHeight = Math.floor(this.getCanvasHeight());
|
||||
const outerHeight = Math.floor(this._outerHeight);
|
||||
const heightRatio = totalHeight / outerHeight;
|
||||
const halfMinimumHeight = Math.floor(Constants.MINIMUM_HEIGHT * this._pixelRatio / 2);
|
||||
|
||||
|
|
|
@ -27,9 +27,6 @@ export class MonospaceLineBreaksComputerFactory implements ILineBreaksComputerFa
|
|||
}
|
||||
|
||||
public createLineBreaksComputer(fontInfo: FontInfo, tabSize: number, wrappingColumn: number, wrappingIndent: WrappingIndent): ILineBreaksComputer {
|
||||
tabSize = tabSize | 0; //@perf
|
||||
wrappingColumn = +wrappingColumn; //@perf
|
||||
|
||||
const requests: string[] = [];
|
||||
const injectedTexts: (LineInjectedText[] | null)[] = [];
|
||||
const previousBreakingData: (ModelLineProjectionData | null)[] = [];
|
||||
|
@ -40,7 +37,7 @@ export class MonospaceLineBreaksComputerFactory implements ILineBreaksComputerFa
|
|||
previousBreakingData.push(previousLineBreakData);
|
||||
},
|
||||
finalize: () => {
|
||||
const columnsForFullWidthChar = fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth; //@perf
|
||||
const columnsForFullWidthChar = fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth;
|
||||
let result: (ModelLineProjectionData | null)[] = [];
|
||||
for (let i = 0, len = requests.length; i < len; i++) {
|
||||
const injectedText = injectedTexts[i];
|
||||
|
|
|
@ -151,7 +151,7 @@ export class PrefixSumComputer {
|
|||
}
|
||||
|
||||
public getIndexOf(sum: number): PrefixSumIndexOfResult {
|
||||
sum = Math.floor(sum); //@perf
|
||||
sum = Math.floor(sum);
|
||||
|
||||
// Compute all sums (to get a fully valid prefixSum)
|
||||
this.getTotalSum();
|
||||
|
|
|
@ -11,6 +11,7 @@ import { IModelDecoration, ITextModel, PositionAffinity } from 'vs/editor/common
|
|||
import { IViewModelLines } from 'vs/editor/common/viewModel/viewModelLines';
|
||||
import { ICoordinatesConverter, InlineDecoration, InlineDecorationType, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel';
|
||||
import { filterValidationDecorations } from 'vs/editor/common/config/editorOptions';
|
||||
import { StandardTokenType } from 'vs/editor/common/modes';
|
||||
|
||||
export interface IDecorationsViewportData {
|
||||
/**
|
||||
|
@ -107,6 +108,7 @@ export class ViewModelDecorations implements IDisposable {
|
|||
|
||||
private _getDecorationsViewportData(viewportRange: Range): IDecorationsViewportData {
|
||||
const modelDecorations = this._linesCollection.getDecorationsInRange(viewportRange, this.editorId, filterValidationDecorations(this.configuration.options));
|
||||
|
||||
const startLineNumber = viewportRange.startLineNumber;
|
||||
const endLineNumber = viewportRange.endLineNumber;
|
||||
|
||||
|
@ -120,6 +122,18 @@ export class ViewModelDecorations implements IDisposable {
|
|||
let modelDecoration = modelDecorations[i];
|
||||
let decorationOptions = modelDecoration.options;
|
||||
|
||||
if (decorationOptions.hideInCommentTokens) {
|
||||
let allTokensComments = testTokensInRange(
|
||||
this.model,
|
||||
modelDecoration.range,
|
||||
(tokenType) => tokenType === StandardTokenType.Comment
|
||||
);
|
||||
|
||||
if (allTokensComments) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let viewModelDecoration = this._getOrCreateViewModelDecoration(modelDecoration);
|
||||
let viewRange = viewModelDecoration.range;
|
||||
|
||||
|
@ -161,3 +175,33 @@ export class ViewModelDecorations implements IDisposable {
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the callback for every token that intersects the range.
|
||||
* If the callback returns `false`, iteration stops and `false` is returned.
|
||||
* Otherwise, `true` is returned.
|
||||
*/
|
||||
function testTokensInRange(model: ITextModel, range: Range, callback: (tokenType: StandardTokenType) => boolean): boolean {
|
||||
for (let lineNumber = range.startLineNumber; lineNumber <= range.endLineNumber; lineNumber++) {
|
||||
const lineTokens = model.getLineTokens(lineNumber);
|
||||
const isFirstLine = lineNumber === range.startLineNumber;
|
||||
const isEndLine = lineNumber === range.endLineNumber;
|
||||
|
||||
let tokenIdx = isFirstLine ? lineTokens.findTokenIndexAtOffset(range.startColumn - 1) : 0;
|
||||
while (tokenIdx < lineTokens.getCount()) {
|
||||
if (isEndLine) {
|
||||
const startOffset = lineTokens.getStartOffset(tokenIdx);
|
||||
if (startOffset > range.endColumn - 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const callbackResult = callback(lineTokens.getStandardTokenType(tokenIdx));
|
||||
if (!callbackResult) {
|
||||
return false;
|
||||
}
|
||||
tokenIdx++;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import * as assert from 'assert';
|
|||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { assertType } from 'vs/base/common/types';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
|
@ -27,10 +28,11 @@ const testProvider = {
|
|||
};
|
||||
}
|
||||
};
|
||||
|
||||
suite('CodeActionModel', () => {
|
||||
|
||||
const languageId = 'foo-lang';
|
||||
let uri = URI.parse('untitled:path');
|
||||
const uri = URI.parse('untitled:path');
|
||||
let model: TextModel;
|
||||
let markerService: MarkerService;
|
||||
let editor: ICodeEditor;
|
||||
|
@ -51,51 +53,15 @@ suite('CodeActionModel', () => {
|
|||
markerService.dispose();
|
||||
});
|
||||
|
||||
test('Orcale -> marker added', done => {
|
||||
const reg = modes.CodeActionProviderRegistry.register(languageId, testProvider);
|
||||
disposables.add(reg);
|
||||
test('Oracle -> marker added', async () => {
|
||||
let done: () => void;
|
||||
const donePromise = new Promise<void>(resolve => {
|
||||
done = resolve;
|
||||
});
|
||||
await runWithFakedTimers({ useFakeTimers: true }, () => {
|
||||
const reg = modes.CodeActionProviderRegistry.register(languageId, testProvider);
|
||||
disposables.add(reg);
|
||||
|
||||
const contextKeys = new MockContextKeyService();
|
||||
const model = disposables.add(new CodeActionModel(editor, markerService, contextKeys, undefined));
|
||||
disposables.add(model.onDidChangeState((e: CodeActionsState.State) => {
|
||||
assertType(e.type === CodeActionsState.Type.Triggered);
|
||||
|
||||
assert.strictEqual(e.trigger.type, modes.CodeActionTriggerType.Auto);
|
||||
assert.ok(e.actions);
|
||||
|
||||
e.actions.then(fixes => {
|
||||
model.dispose();
|
||||
assert.strictEqual(fixes.validActions.length, 1);
|
||||
done();
|
||||
}, done);
|
||||
}));
|
||||
|
||||
// start here
|
||||
markerService.changeOne('fake', uri, [{
|
||||
startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 6,
|
||||
message: 'error',
|
||||
severity: 1,
|
||||
code: '',
|
||||
source: ''
|
||||
}]);
|
||||
|
||||
});
|
||||
|
||||
test('Orcale -> position changed', () => {
|
||||
const reg = modes.CodeActionProviderRegistry.register(languageId, testProvider);
|
||||
disposables.add(reg);
|
||||
|
||||
markerService.changeOne('fake', uri, [{
|
||||
startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 6,
|
||||
message: 'error',
|
||||
severity: 1,
|
||||
code: '',
|
||||
source: ''
|
||||
}]);
|
||||
|
||||
editor.setPosition({ lineNumber: 2, column: 1 });
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const contextKeys = new MockContextKeyService();
|
||||
const model = disposables.add(new CodeActionModel(editor, markerService, contextKeys, undefined));
|
||||
disposables.add(model.onDidChangeState((e: CodeActionsState.State) => {
|
||||
|
@ -103,18 +69,62 @@ suite('CodeActionModel', () => {
|
|||
|
||||
assert.strictEqual(e.trigger.type, modes.CodeActionTriggerType.Auto);
|
||||
assert.ok(e.actions);
|
||||
|
||||
e.actions.then(fixes => {
|
||||
model.dispose();
|
||||
assert.strictEqual(fixes.validActions.length, 1);
|
||||
resolve(undefined);
|
||||
}, reject);
|
||||
done();
|
||||
}, done);
|
||||
}));
|
||||
|
||||
// start here
|
||||
editor.setPosition({ lineNumber: 1, column: 1 });
|
||||
markerService.changeOne('fake', uri, [{
|
||||
startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 6,
|
||||
message: 'error',
|
||||
severity: 1,
|
||||
code: '',
|
||||
source: ''
|
||||
}]);
|
||||
return donePromise;
|
||||
});
|
||||
});
|
||||
|
||||
test('Lightbulb is in the wrong place, #29933', async function () {
|
||||
test('Oracle -> position changed', async () => {
|
||||
await runWithFakedTimers({ useFakeTimers: true }, () => {
|
||||
const reg = modes.CodeActionProviderRegistry.register(languageId, testProvider);
|
||||
disposables.add(reg);
|
||||
|
||||
markerService.changeOne('fake', uri, [{
|
||||
startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 6,
|
||||
message: 'error',
|
||||
severity: 1,
|
||||
code: '',
|
||||
source: ''
|
||||
}]);
|
||||
|
||||
editor.setPosition({ lineNumber: 2, column: 1 });
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const contextKeys = new MockContextKeyService();
|
||||
const model = disposables.add(new CodeActionModel(editor, markerService, contextKeys, undefined));
|
||||
disposables.add(model.onDidChangeState((e: CodeActionsState.State) => {
|
||||
assertType(e.type === CodeActionsState.Type.Triggered);
|
||||
|
||||
assert.strictEqual(e.trigger.type, modes.CodeActionTriggerType.Auto);
|
||||
assert.ok(e.actions);
|
||||
e.actions.then(fixes => {
|
||||
model.dispose();
|
||||
assert.strictEqual(fixes.validActions.length, 1);
|
||||
resolve(undefined);
|
||||
}, reject);
|
||||
}));
|
||||
// start here
|
||||
editor.setPosition({ lineNumber: 1, column: 1 });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('Lightbulb is in the wrong place, #29933', async () => {
|
||||
const reg = modes.CodeActionProviderRegistry.register(languageId, {
|
||||
provideCodeActions(_doc, _range): modes.CodeActionList {
|
||||
return { actions: [], dispose() { /* noop*/ } };
|
||||
|
@ -122,68 +132,77 @@ suite('CodeActionModel', () => {
|
|||
});
|
||||
disposables.add(reg);
|
||||
|
||||
editor.getModel()!.setValue('// @ts-check\n2\ncon\n');
|
||||
await runWithFakedTimers({ useFakeTimers: true }, async () => {
|
||||
editor.getModel()!.setValue('// @ts-check\n2\ncon\n');
|
||||
|
||||
markerService.changeOne('fake', uri, [{
|
||||
startLineNumber: 3, startColumn: 1, endLineNumber: 3, endColumn: 4,
|
||||
message: 'error',
|
||||
severity: 1,
|
||||
code: '',
|
||||
source: ''
|
||||
}]);
|
||||
markerService.changeOne('fake', uri, [{
|
||||
startLineNumber: 3, startColumn: 1, endLineNumber: 3, endColumn: 4,
|
||||
message: 'error',
|
||||
severity: 1,
|
||||
code: '',
|
||||
source: ''
|
||||
}]);
|
||||
|
||||
// case 1 - drag selection over multiple lines -> range of enclosed marker, position or marker
|
||||
await new Promise(resolve => {
|
||||
// case 1 - drag selection over multiple lines -> range of enclosed marker, position or marker
|
||||
await new Promise(resolve => {
|
||||
const contextKeys = new MockContextKeyService();
|
||||
const model = disposables.add(new CodeActionModel(editor, markerService, contextKeys, undefined));
|
||||
disposables.add(model.onDidChangeState((e: CodeActionsState.State) => {
|
||||
assertType(e.type === CodeActionsState.Type.Triggered);
|
||||
|
||||
assert.strictEqual(e.trigger.type, modes.CodeActionTriggerType.Auto);
|
||||
const selection = <Selection>e.rangeOrSelection;
|
||||
assert.strictEqual(selection.selectionStartLineNumber, 1);
|
||||
assert.strictEqual(selection.selectionStartColumn, 1);
|
||||
assert.strictEqual(selection.endLineNumber, 4);
|
||||
assert.strictEqual(selection.endColumn, 1);
|
||||
assert.strictEqual(e.position.lineNumber, 3);
|
||||
assert.strictEqual(e.position.column, 1);
|
||||
model.dispose();
|
||||
resolve(undefined);
|
||||
}, 5));
|
||||
|
||||
editor.setSelection({ startLineNumber: 1, startColumn: 1, endLineNumber: 4, endColumn: 1 });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('Oracle -> should only auto trigger once for cursor and marker update right after each other', async () => {
|
||||
let done: () => void;
|
||||
const donePromise = new Promise<void>(resolve => { done = resolve; });
|
||||
|
||||
await runWithFakedTimers({ useFakeTimers: true }, () => {
|
||||
const reg = modes.CodeActionProviderRegistry.register(languageId, testProvider);
|
||||
disposables.add(reg);
|
||||
|
||||
let triggerCount = 0;
|
||||
const contextKeys = new MockContextKeyService();
|
||||
const model = disposables.add(new CodeActionModel(editor, markerService, contextKeys, undefined));
|
||||
disposables.add(model.onDidChangeState((e: CodeActionsState.State) => {
|
||||
assertType(e.type === CodeActionsState.Type.Triggered);
|
||||
|
||||
assert.strictEqual(e.trigger.type, modes.CodeActionTriggerType.Auto);
|
||||
const selection = <Selection>e.rangeOrSelection;
|
||||
assert.strictEqual(selection.selectionStartLineNumber, 1);
|
||||
assert.strictEqual(selection.selectionStartColumn, 1);
|
||||
assert.strictEqual(selection.endLineNumber, 4);
|
||||
assert.strictEqual(selection.endColumn, 1);
|
||||
assert.strictEqual(e.position.lineNumber, 3);
|
||||
assert.strictEqual(e.position.column, 1);
|
||||
model.dispose();
|
||||
resolve(undefined);
|
||||
}, 5));
|
||||
++triggerCount;
|
||||
|
||||
// give time for second trigger before completing test
|
||||
setTimeout(() => {
|
||||
model.dispose();
|
||||
assert.strictEqual(triggerCount, 1);
|
||||
done();
|
||||
}, 0);
|
||||
}, 5 /*delay*/));
|
||||
|
||||
markerService.changeOne('fake', uri, [{
|
||||
startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 6,
|
||||
message: 'error',
|
||||
severity: 1,
|
||||
code: '',
|
||||
source: ''
|
||||
}]);
|
||||
|
||||
editor.setSelection({ startLineNumber: 1, startColumn: 1, endLineNumber: 4, endColumn: 1 });
|
||||
|
||||
return donePromise;
|
||||
});
|
||||
});
|
||||
|
||||
test('Orcale -> should only auto trigger once for cursor and marker update right after each other', done => {
|
||||
const reg = modes.CodeActionProviderRegistry.register(languageId, testProvider);
|
||||
disposables.add(reg);
|
||||
|
||||
let triggerCount = 0;
|
||||
const contextKeys = new MockContextKeyService();
|
||||
const model = disposables.add(new CodeActionModel(editor, markerService, contextKeys, undefined));
|
||||
disposables.add(model.onDidChangeState((e: CodeActionsState.State) => {
|
||||
assertType(e.type === CodeActionsState.Type.Triggered);
|
||||
|
||||
assert.strictEqual(e.trigger.type, modes.CodeActionTriggerType.Auto);
|
||||
++triggerCount;
|
||||
|
||||
// give time for second trigger before completing test
|
||||
setTimeout(() => {
|
||||
model.dispose();
|
||||
assert.strictEqual(triggerCount, 1);
|
||||
done();
|
||||
}, 50);
|
||||
}, 5 /*delay*/));
|
||||
|
||||
markerService.changeOne('fake', uri, [{
|
||||
startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 6,
|
||||
message: 'error',
|
||||
severity: 1,
|
||||
code: '',
|
||||
source: ''
|
||||
}]);
|
||||
|
||||
editor.setSelection({ startLineNumber: 1, startColumn: 1, endLineNumber: 4, endColumn: 1 });
|
||||
});
|
||||
});
|
||||
|
|
|
@ -118,24 +118,35 @@ export class MarkdownHoverParticipant implements IEditorHoverParticipant<Markdow
|
|||
}
|
||||
|
||||
public renderHoverParts(hoverParts: MarkdownHover[], fragment: DocumentFragment, statusBar: IEditorHoverStatusBar): IDisposable {
|
||||
const disposables = new DisposableStore();
|
||||
for (const hoverPart of hoverParts) {
|
||||
for (const contents of hoverPart.contents) {
|
||||
if (isEmptyMarkdownString(contents)) {
|
||||
continue;
|
||||
}
|
||||
const markdownHoverElement = $('div.hover-row.markdown-hover');
|
||||
const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents'));
|
||||
const renderer = disposables.add(new MarkdownRenderer({ editor: this._editor }, this._modeService, this._openerService));
|
||||
disposables.add(renderer.onDidRenderAsync(() => {
|
||||
hoverContentsElement.className = 'hover-contents code-hover-contents';
|
||||
this._hover.onContentsChanged();
|
||||
}));
|
||||
const renderedContents = disposables.add(renderer.render(contents));
|
||||
hoverContentsElement.appendChild(renderedContents.element);
|
||||
fragment.appendChild(markdownHoverElement);
|
||||
}
|
||||
}
|
||||
return disposables;
|
||||
return renderMarkdownHovers(hoverParts, fragment, this._editor, this._hover, this._modeService, this._openerService);
|
||||
}
|
||||
}
|
||||
|
||||
export function renderMarkdownHovers(
|
||||
hoverParts: MarkdownHover[],
|
||||
fragment: DocumentFragment,
|
||||
editor: ICodeEditor,
|
||||
hover: IEditorHover,
|
||||
modeService: IModeService,
|
||||
openerService: IOpenerService,
|
||||
): IDisposable {
|
||||
const disposables = new DisposableStore();
|
||||
for (const hoverPart of hoverParts) {
|
||||
for (const contents of hoverPart.contents) {
|
||||
if (isEmptyMarkdownString(contents)) {
|
||||
continue;
|
||||
}
|
||||
const markdownHoverElement = $('div.hover-row.markdown-hover');
|
||||
const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents'));
|
||||
const renderer = disposables.add(new MarkdownRenderer({ editor }, modeService, openerService));
|
||||
disposables.add(renderer.onDidRenderAsync(() => {
|
||||
hoverContentsElement.className = 'hover-contents code-hover-contents';
|
||||
hover.onContentsChanged();
|
||||
}));
|
||||
const renderedContents = disposables.add(renderer.render(contents));
|
||||
hoverContentsElement.appendChild(renderedContents.element);
|
||||
fragment.appendChild(markdownHoverElement);
|
||||
}
|
||||
}
|
||||
return disposables;
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c
|
|||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { Context as SuggestContext } from 'vs/editor/contrib/suggest/suggest';
|
||||
import { UnicodeHighlighterHoverParticipant } from 'vs/editor/contrib/unicodeHighlighter/unicodeHighlighter';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
|
@ -223,6 +224,7 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I
|
|||
instantiationService.createInstance(ColorHoverParticipant, editor, this),
|
||||
instantiationService.createInstance(MarkdownHoverParticipant, editor, this),
|
||||
instantiationService.createInstance(InlineCompletionsHoverParticipant, editor, this),
|
||||
instantiationService.createInstance(UnicodeHighlighterHoverParticipant, editor, this),
|
||||
instantiationService.createInstance(MarkerHoverParticipant, editor, this),
|
||||
];
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
|
|||
import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { CompletionItemInsertTextRule } from 'vs/editor/common/modes';
|
||||
import { CompletionItemInsertTextRule, CompletionItemKind } from 'vs/editor/common/modes';
|
||||
import { SnippetParser } from 'vs/editor/contrib/snippet/snippetParser';
|
||||
import { SnippetSession } from 'vs/editor/contrib/snippet/snippetSession';
|
||||
import { CompletionItem } from 'vs/editor/contrib/suggest/suggest';
|
||||
|
@ -22,14 +22,20 @@ export interface SuggestWidgetState {
|
|||
/**
|
||||
* Represents the currently selected item in the suggest widget as inline completion, if possible.
|
||||
*/
|
||||
selectedItemAsInlineCompletion: NormalizedInlineCompletion | undefined;
|
||||
selectedItem: SuggestItemInfo | undefined;
|
||||
}
|
||||
|
||||
export interface SuggestItemInfo {
|
||||
normalizedInlineCompletion: NormalizedInlineCompletion;
|
||||
isSnippetText: boolean;
|
||||
completionItemKind: CompletionItemKind;
|
||||
}
|
||||
|
||||
export class SuggestWidgetInlineCompletionProvider extends Disposable {
|
||||
private isSuggestWidgetVisible: boolean = false;
|
||||
private isShiftKeyPressed = false;
|
||||
private _isActive = false;
|
||||
private _currentInlineCompletion: NormalizedInlineCompletion | undefined = undefined;
|
||||
private _currentSuggestItemInfo: SuggestItemInfo | undefined = undefined;
|
||||
private readonly onDidChangeEmitter = new Emitter<void>();
|
||||
|
||||
public readonly onDidChange = this.onDidChangeEmitter.event;
|
||||
|
@ -51,7 +57,7 @@ export class SuggestWidgetInlineCompletionProvider extends Disposable {
|
|||
if (!this._isActive) {
|
||||
return undefined;
|
||||
}
|
||||
return { selectedItemAsInlineCompletion: this._currentInlineCompletion };
|
||||
return { selectedItem: this._currentSuggestItemInfo };
|
||||
}
|
||||
|
||||
constructor(
|
||||
|
@ -88,8 +94,8 @@ export class SuggestWidgetInlineCompletionProvider extends Disposable {
|
|||
|
||||
const candidates = suggestItems
|
||||
.map((suggestItem, index) => {
|
||||
const inlineSuggestItem = suggestionToInlineCompletion(suggestController, position, suggestItem, this.isShiftKeyPressed);
|
||||
const normalizedSuggestItem = minimizeInlineCompletion(textModel, inlineSuggestItem);
|
||||
const inlineSuggestItem = suggestionToSuggestItemInfo(suggestController, position, suggestItem, this.isShiftKeyPressed);
|
||||
const normalizedSuggestItem = minimizeInlineCompletion(textModel, inlineSuggestItem?.normalizedInlineCompletion);
|
||||
if (!normalizedSuggestItem) {
|
||||
return undefined;
|
||||
}
|
||||
|
@ -138,10 +144,10 @@ export class SuggestWidgetInlineCompletionProvider extends Disposable {
|
|||
}
|
||||
|
||||
private update(newActive: boolean): void {
|
||||
const newInlineCompletion = this.getInlineCompletion();
|
||||
const newInlineCompletion = this.getSuggestItemInfo();
|
||||
let shouldFire = false;
|
||||
if (!normalizedInlineCompletionsEquals(this._currentInlineCompletion, newInlineCompletion)) {
|
||||
this._currentInlineCompletion = newInlineCompletion;
|
||||
if (!suggestItemInfoEquals(this._currentSuggestItemInfo, newInlineCompletion)) {
|
||||
this._currentSuggestItemInfo = newInlineCompletion;
|
||||
shouldFire = true;
|
||||
}
|
||||
if (this._isActive !== newActive) {
|
||||
|
@ -153,7 +159,7 @@ export class SuggestWidgetInlineCompletionProvider extends Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
private getInlineCompletion(): NormalizedInlineCompletion | undefined {
|
||||
private getSuggestItemInfo(): SuggestItemInfo | undefined {
|
||||
const suggestController = SuggestController.get(this.editor);
|
||||
if (!suggestController) {
|
||||
return undefined;
|
||||
|
@ -167,7 +173,7 @@ export class SuggestWidgetInlineCompletionProvider extends Disposable {
|
|||
}
|
||||
|
||||
// TODO: item.isResolved
|
||||
return suggestionToInlineCompletion(
|
||||
return suggestionToSuggestItemInfo(
|
||||
suggestController,
|
||||
this.editor.getPosition(),
|
||||
focusedItem.item,
|
||||
|
@ -200,17 +206,35 @@ export function rangeStartsWith(rangeToTest: Range, prefix: Range): boolean {
|
|||
);
|
||||
}
|
||||
|
||||
function suggestionToInlineCompletion(suggestController: SuggestController, position: Position, item: CompletionItem, toggleMode: boolean): NormalizedInlineCompletion | undefined {
|
||||
function suggestItemInfoEquals(a: SuggestItemInfo | undefined, b: SuggestItemInfo | undefined): boolean {
|
||||
if (a === b) {
|
||||
return true;
|
||||
}
|
||||
if (!a || !b) {
|
||||
return false;
|
||||
}
|
||||
return a.completionItemKind === b.completionItemKind &&
|
||||
a.isSnippetText === b.isSnippetText &&
|
||||
normalizedInlineCompletionsEquals(a.normalizedInlineCompletion, b.normalizedInlineCompletion);
|
||||
}
|
||||
|
||||
function suggestionToSuggestItemInfo(suggestController: SuggestController, position: Position, item: CompletionItem, toggleMode: boolean): SuggestItemInfo | undefined {
|
||||
// additionalTextEdits might not be resolved here, this could be problematic.
|
||||
if (Array.isArray(item.completion.additionalTextEdits) && item.completion.additionalTextEdits.length > 0) {
|
||||
// cannot represent additional text edits
|
||||
return {
|
||||
text: '',
|
||||
range: Range.fromPositions(position, position),
|
||||
completionItemKind: item.completion.kind,
|
||||
isSnippetText: false,
|
||||
normalizedInlineCompletion: {
|
||||
// Dummy element, so that space is reserved, but no text is shown
|
||||
range: Range.fromPositions(position, position),
|
||||
text: ''
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
let { insertText } = item.completion;
|
||||
let isSnippetText = false;
|
||||
if (item.completion.insertTextRules! & CompletionItemInsertTextRule.InsertAsSnippet) {
|
||||
const snippet = new SnippetParser().parse(insertText);
|
||||
const model = suggestController.editor.getModel()!;
|
||||
|
@ -223,14 +247,19 @@ function suggestionToInlineCompletion(suggestController: SuggestController, posi
|
|||
|
||||
SnippetSession.adjustWhitespace(model, position, snippet, true, true);
|
||||
insertText = snippet.toString();
|
||||
isSnippetText = true;
|
||||
}
|
||||
|
||||
const info = suggestController.getOverwriteInfo(item, toggleMode);
|
||||
return {
|
||||
text: insertText,
|
||||
range: Range.fromPositions(
|
||||
position.delta(0, -info.overwriteBefore),
|
||||
position.delta(0, Math.max(info.overwriteAfter, 0))
|
||||
),
|
||||
isSnippetText,
|
||||
completionItemKind: item.completion.kind,
|
||||
normalizedInlineCompletion: {
|
||||
text: insertText,
|
||||
range: Range.fromPositions(
|
||||
position.delta(0, -info.overwriteBefore),
|
||||
position.delta(0, Math.max(info.overwriteAfter, 0))
|
||||
),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue