Merge remote-tracking branch 'origin/main' into tyriar/persist_tests

This commit is contained in:
Daniel Imms 2021-11-23 12:47:36 -08:00
commit ef440e563d
263 changed files with 5792 additions and 2634 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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'))

View file

@ -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

View file

@ -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'))

View file

@ -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

View file

@ -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'

View file

@ -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);
});

View file

@ -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);
});

View 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);
});
}

View 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);
});
}

View file

@ -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);
});

View file

@ -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);
});

View file

@ -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);
});

View file

@ -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);
});

View file

@ -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'))

View file

@ -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'))

View file

@ -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());
}
}

View file

@ -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",

View file

@ -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",

View file

@ -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"

View file

@ -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",

View file

@ -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) {

View file

@ -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"

View file

@ -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);

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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));
}
};
}

View 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`;
}

View file

@ -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) });
}

View file

@ -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;
};

View file

@ -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';

View file

@ -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();
}

View file

@ -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);

View file

@ -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: {
},
};
};

View file

@ -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"
}
}

View file

@ -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"
}

View file

@ -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),

View file

@ -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 };
}

View file

@ -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'));

View file

@ -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(

View file

@ -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",
]

View file

@ -6,6 +6,7 @@
"license": "MIT",
"enabledApiProposals": [
"authSession",
"contribViewsRemote",
"customEditorMove",
"diffCommand",
"documentFiltersExclusive",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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"

View file

@ -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"

View file

@ -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');

View file

@ -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: [

View file

@ -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() {

View file

@ -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 {

View file

@ -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() : '';
}
}

View file

@ -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 || []] });

View file

@ -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();

View file

@ -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

View file

@ -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();

View file

@ -54,8 +54,7 @@ export class ModifierLabelProvider {
*/
export const UILabelProvider = new ModifierLabelProvider(
{
// allow-any-unicode-next-line
ctrlKey: '⌃',
ctrlKey: '\u2303',
shiftKey: '⇧',
altKey: '⌥',
metaKey: '⌘',

View file

@ -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) {

View file

@ -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 {

View file

@ -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

View file

@ -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];
}

View file

@ -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;

View file

@ -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);

View file

@ -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();

View file

@ -38,7 +38,7 @@ body {
}
.cpu {
width: 45px;
width: 60px;
}
.pid {

View file

@ -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);
}

View file

@ -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({

View file

@ -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/

View file

@ -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 {

View file

@ -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) {

View file

@ -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 {

View file

@ -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',

View file

@ -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);
}
}

View file

@ -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.
*/

View file

@ -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;
}

View file

@ -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.

View file

@ -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' });

View file

@ -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;

View file

@ -677,6 +677,8 @@ export interface InlineCompletionContext {
export interface SelectedSuggestionInfo {
range: IRange;
text: string;
isSnippetText: boolean;
completionKind: CompletionItemKind;
}
export interface InlineCompletion {

View file

@ -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;
}
}
/**

View file

@ -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 {

View file

@ -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;
}

View 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[];
}

View file

@ -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> {

View file

@ -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;

View file

@ -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);

View file

@ -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
}
/**

View file

@ -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) => {

View file

@ -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);

View file

@ -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];

View file

@ -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();

View file

@ -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;
}

View file

@ -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 });
});
});

View file

@ -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;
}

View file

@ -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),
];

View file

@ -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