diff --git a/.eslintrc.json b/.eslintrc.json index e55fa836ac9..70012db4e74 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -951,6 +951,7 @@ "**/test/automation/**", "@vscode/*", "@parcel/*", + "playwright-core/**", "*" // node modules ] }, @@ -1064,7 +1065,7 @@ { "files": [ "**/vscode.d.ts", - "**/vscode.proposed.d.ts" + "**/vscode.proposed.*.d.ts" ], "rules": { "vscode-dts-create-func": "warn", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bccbb3fef51..81394730d69 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,9 +75,20 @@ jobs: - name: Run Unit Tests (Browser) run: yarn test-browser --browser chromium + - name: Compile Integration Tests + run: yarn --cwd test/integration/browser compile + - name: Run Integration Tests (Electron) run: .\scripts\test-integration.bat + - name: Run Integration Tests (Browser) + timeout-minutes: 10 + run: .\resources\server\test\test-web-integration.bat --browser firefox + + - name: Run Remote Integration Tests (Electron) + timeout-minutes: 10 + run: .\resources\server\test\test-remote-integration.bat + linux: name: Linux runs-on: ubuntu-latest @@ -140,10 +151,22 @@ jobs: id: browser-unit-tests run: DISPLAY=:10 yarn test-browser --browser chromium + - name: Compile Integration Tests + run: yarn --cwd test/integration/browser compile + - name: Run Integration Tests (Electron) id: electron-integration-tests run: DISPLAY=:10 ./scripts/test-integration.sh + - name: Run Integration Tests (Browser) + id: browser-integration-tests + run: DISPLAY=:10 ./resources/server/test/test-web-integration.sh --browser chromium + + - name: Run Remote Integration Tests (Electron) + id: electron-remote-integration-tests + timeout-minutes: 7 + run: DISPLAY=:10 ./resources/server/test/test-remote-integration.sh + darwin: name: macOS runs-on: macos-latest @@ -202,9 +225,19 @@ jobs: - name: Run Unit Tests (Browser) run: DISPLAY=:10 yarn test-browser --browser chromium + - name: Compile Integration Tests + run: yarn --cwd test/integration/browser compile + - name: Run Integration Tests (Electron) run: DISPLAY=:10 ./scripts/test-integration.sh + - name: Run Integration Tests (Browser) + run: DISPLAY=:10 ./resources/server/test/test-web-integration.sh --browser webkit + + - name: Run Remote Integration Tests (Electron) + timeout-minutes: 7 + run: DISPLAY=:10 ./resources/server/test/test-remote-integration.sh + hygiene: name: Hygiene, Layering and Monaco Editor runs-on: ubuntu-latest diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5d547535187..3d6c01e8335 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,7 +18,7 @@ See the [Feedback Channels](https://github.com/microsoft/vscode/wiki/Feedback-Ch ## Reporting Issues -Have you identified a reproducible problem in VS Code? Have a feature request? We want to hear about it! Here's how you can make reporting your issue as effective as possible. +Have you identified a reproducible problem in VS Code? Have a feature request? We want to hear about it! Here's how you can report your issue as effectively as possible. ### Identify Where to Report @@ -43,7 +43,7 @@ If you cannot find an existing issue that describes your bug or feature, create File a single issue per problem and feature request. Do not enumerate multiple bugs or feature requests in the same issue. -Do not add your issue as a comment to an existing issue unless it's for the identical input. Many issues look similar, but have different causes. +Do not add your issue as a comment to an existing issue unless it's for the identical input. Many issues look similar but have different causes. The more information you can provide, the more likely someone will be successful at reproducing the issue and finding a fix. diff --git a/README.md b/README.md index 1883fe4b76b..f6e8f39e2d5 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ This repository ("`Code - OSS`") is where we (Microsoft) develop the [Visual Stu VS Code in action

-[Visual Studio Code](https://code.visualstudio.com) is a distribution of the `Code - OSS` repository with Microsoft specific customizations released under a traditional [Microsoft product license](https://code.visualstudio.com/License/). +[Visual Studio Code](https://code.visualstudio.com) is a distribution of the `Code - OSS` repository with Microsoft-specific customizations released under a traditional [Microsoft product license](https://code.visualstudio.com/License/). [Visual Studio Code](https://code.visualstudio.com) combines the simplicity of a code editor with what developers need for their core edit-build-debug cycle. It provides comprehensive code editing, navigation, and understanding support along with lightweight debugging, a rich extensibility model, and lightweight integration with existing tools. @@ -49,11 +49,11 @@ See our [wiki](https://github.com/microsoft/vscode/wiki/Feedback-Channels) for a ## Related Projects -Many of the core components and extensions to VS Code live in their own repositories on GitHub. For example, the [node debug adapter](https://github.com/microsoft/vscode-node-debug) and the [mono debug adapter](https://github.com/microsoft/vscode-mono-debug) have their own repositories. For a complete list, please visit the [Related Projects](https://github.com/microsoft/vscode/wiki/Related-Projects) page on our [wiki](https://github.com/microsoft/vscode/wiki). +Many of the core components and extensions to VS Code live in their own repositories on GitHub. For example, the [node debug adapter](https://github.com/microsoft/vscode-node-debug) and the [mono debug adapter](https://github.com/microsoft/vscode-mono-debug) repositories are separate from each other. For a complete list, please visit the [Related Projects](https://github.com/microsoft/vscode/wiki/Related-Projects) page on our [wiki](https://github.com/microsoft/vscode/wiki). ## Bundled Extensions -VS Code includes a set of built-in extensions located in the [extensions](extensions) folder, including grammars and snippets for many languages. Extensions that provide rich language support (code completion, Go to Definition) for a language have the suffix `language-features`. For example, the `json` extension provides coloring for `JSON` and the `json-language-features` provides rich language support for `JSON`. +VS Code includes a set of built-in extensions located in the [extensions](extensions) folder, including grammars and snippets for many languages. Extensions that provide rich language support (code completion, Go to Definition) for a language have the suffix `language-features`. For example, the `json` extension provides coloring for `JSON` and the `json-language-features` extension provides rich language support for `JSON`. ## Development Container diff --git a/build/.cachesalt b/build/.cachesalt index 93303e0b977..84414b19100 100644 --- a/build/.cachesalt +++ b/build/.cachesalt @@ -1 +1 @@ -2021-08-20T17:19:02.924Z +2021-11-19T08:23:35Z diff --git a/build/.moduleignore b/build/.moduleignore index 5bbca4640b8..233ca72a48a 100644 --- a/build/.moduleignore +++ b/build/.moduleignore @@ -51,8 +51,6 @@ spdlog/test/** spdlog/*.yml !spdlog/build/Release/*.node -jschardet/dist/** - windows-foreground-love/binding.gyp windows-foreground-love/build/** windows-foreground-love/src/** @@ -132,7 +130,9 @@ node-addon-api/**/* **/*.ts !typescript/**/*.d.ts -jschardet/dist/** +jschardet/index.js +jschardet/src/** +jschardet/dist/jschardet.js es6-promise/lib/** diff --git a/build/azure-pipelines/common/createAsset.js b/build/azure-pipelines/common/createAsset.js index 86e207f63a4..360b1d74363 100644 --- a/build/azure-pipelines/common/createAsset.js +++ b/build/azure-pipelines/common/createAsset.js @@ -5,11 +5,11 @@ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); const fs = require("fs"); -const url = require("url"); const crypto = require("crypto"); -const azure = require("azure-storage"); +const storage_blob_1 = require("@azure/storage-blob"); const mime = require("mime"); const cosmos_1 = require("@azure/cosmos"); +const identity_1 = require("@azure/identity"); const retry_1 = require("./retry"); if (process.argv.length !== 8) { console.error('Usage: node createAsset.js PRODUCT OS ARCH TYPE NAME FILE'); @@ -118,20 +118,6 @@ function hashStream(hashName, stream) { .on('close', () => c(shasum.digest('hex'))); }); } -async function doesAssetExist(blobService, quality, blobName) { - const existsResult = await new Promise((c, e) => blobService.doesBlobExist(quality, blobName, (err, r) => err ? e(err) : c(r))); - return existsResult.exists; -} -async function uploadBlob(blobService, quality, blobName, filePath, fileName) { - const blobOptions = { - contentSettings: { - contentType: mime.lookup(filePath), - contentDisposition: `attachment; filename="${fileName}"`, - cacheControl: 'max-age=31536000, public' - } - }; - await new Promise((c, e) => blobService.createBlockBlobFromLocalFile(quality, blobName, filePath, blobOptions, err => err ? e(err) : c())); -} function getEnv(name) { const result = process.env[name]; if (typeof result === 'undefined') { @@ -155,28 +141,35 @@ async function main() { console.log('SHA1:', sha1hash); console.log('SHA256:', sha256hash); const blobName = commit + '/' + fileName; - const storageAccount = process.env['AZURE_STORAGE_ACCOUNT_2']; - const blobService = azure.createBlobService(storageAccount, process.env['AZURE_STORAGE_ACCESS_KEY_2']) - .withFilter(new azure.ExponentialRetryPolicyFilter(20)); - const blobExists = await doesAssetExist(blobService, quality, blobName); + const storagePipelineOptions = { retryOptions: { retryPolicyType: storage_blob_1.StorageRetryPolicyType.EXPONENTIAL, maxTries: 6, tryTimeoutInMs: 10 * 60 * 1000 } }; + const credential = new identity_1.ClientSecretCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], process.env['AZURE_CLIENT_SECRET']); + const blobServiceClient = new storage_blob_1.BlobServiceClient(`https://vscode.blob.core.windows.net`, credential, storagePipelineOptions); + const containerClient = blobServiceClient.getContainerClient(quality); + const blobClient = containerClient.getBlockBlobClient(blobName); + const blobExists = await blobClient.exists(); if (blobExists) { console.log(`Blob ${quality}, ${blobName} already exists, not publishing again.`); return; } - const mooncakeBlobService = azure.createBlobService(storageAccount, process.env['MOONCAKE_STORAGE_ACCESS_KEY'], `${storageAccount}.blob.core.chinacloudapi.cn`) - .withFilter(new azure.ExponentialRetryPolicyFilter(20)); - // mooncake is fussy and far away, this is needed! - blobService.defaultClientRequestTimeoutInMs = 10 * 60 * 1000; - mooncakeBlobService.defaultClientRequestTimeoutInMs = 10 * 60 * 1000; + const mooncakeCredential = new identity_1.ClientSecretCredential(process.env['AZURE_MOONCAKE_TENANT_ID'], process.env['AZURE_MOONCAKE_CLIENT_ID'], process.env['AZURE_MOONCAKE_CLIENT_SECRET']); + const mooncakeBlobServiceClient = new storage_blob_1.BlobServiceClient(`https://vscode.blob.core.chinacloudapi.cn`, mooncakeCredential, storagePipelineOptions); + const mooncakeContainerClient = mooncakeBlobServiceClient.getContainerClient(quality); + const mooncakeBlobClient = mooncakeContainerClient.getBlockBlobClient(blobName); console.log('Uploading blobs to Azure storage and Mooncake Azure storage...'); + const blobOptions = { + blobHTTPHeaders: { + blobContentType: mime.lookup(filePath), + blobContentDisposition: `attachment; filename="${fileName}"`, + blobCacheControl: 'max-age=31536000, public' + } + }; await (0, retry_1.retry)(() => Promise.all([ - uploadBlob(blobService, quality, blobName, filePath, fileName), - uploadBlob(mooncakeBlobService, quality, blobName, filePath, fileName) + blobClient.uploadFile(filePath, blobOptions), + mooncakeBlobClient.uploadFile(filePath, blobOptions) ])); console.log('Blobs successfully uploaded.'); - // TODO: Understand if blobName and blobPath are the same and replace blobPath with blobName if so. const assetUrl = `${process.env['AZURE_CDN_URL']}/${quality}/${blobName}`; - const blobPath = url.parse(assetUrl).path; + const blobPath = new URL(assetUrl).pathname; const mooncakeUrl = `${process.env['MOONCAKE_CDN_URL']}${blobPath}`; const asset = { platform, @@ -192,7 +185,7 @@ async function main() { asset.supportsFastUpdate = true; } console.log('Asset:', JSON.stringify(asset, null, ' ')); - const client = new cosmos_1.CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT'], key: process.env['AZURE_DOCUMENTDB_MASTERKEY'] }); + const client = new cosmos_1.CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT'], aadCredentials: credential }); const scripts = client.database('builds').container(quality).scripts; await (0, retry_1.retry)(() => scripts.storedProcedure('createAsset').execute('', [commit, asset, true])); console.log(` Done ✔️`); diff --git a/build/azure-pipelines/common/createAsset.ts b/build/azure-pipelines/common/createAsset.ts index c7e2c07917e..62414d41fcc 100644 --- a/build/azure-pipelines/common/createAsset.ts +++ b/build/azure-pipelines/common/createAsset.ts @@ -6,12 +6,12 @@ 'use strict'; import * as fs from 'fs'; -import * as url from 'url'; import { Readable } from 'stream'; import * as crypto from 'crypto'; -import * as azure from 'azure-storage'; +import { BlobServiceClient, BlockBlobParallelUploadOptions, StoragePipelineOptions, StorageRetryPolicyType } from '@azure/storage-blob'; import * as mime from 'mime'; import { CosmosClient } from '@azure/cosmos'; +import { ClientSecretCredential } from '@azure/identity'; import { retry } from './retry'; interface Asset { @@ -137,23 +137,6 @@ function hashStream(hashName: string, stream: Readable): Promise { }); } -async function doesAssetExist(blobService: azure.BlobService, quality: string, blobName: string): Promise { - const existsResult = await new Promise((c, e) => blobService.doesBlobExist(quality, blobName, (err, r) => err ? e(err) : c(r))); - return existsResult.exists; -} - -async function uploadBlob(blobService: azure.BlobService, quality: string, blobName: string, filePath: string, fileName: string): Promise { - const blobOptions: azure.BlobService.CreateBlockBlobRequestOptions = { - contentSettings: { - contentType: mime.lookup(filePath), - contentDisposition: `attachment; filename="${fileName}"`, - cacheControl: 'max-age=31536000, public' - } - }; - - await new Promise((c, e) => blobService.createBlockBlobFromLocalFile(quality, blobName, filePath, blobOptions, err => err ? e(err) : c())); -} - function getEnv(name: string): string { const result = process.env[name]; @@ -186,37 +169,44 @@ async function main(): Promise { console.log('SHA256:', sha256hash); const blobName = commit + '/' + fileName; - const storageAccount = process.env['AZURE_STORAGE_ACCOUNT_2']!; - const blobService = azure.createBlobService(storageAccount, process.env['AZURE_STORAGE_ACCESS_KEY_2']!) - .withFilter(new azure.ExponentialRetryPolicyFilter(20)); + const storagePipelineOptions: StoragePipelineOptions = { retryOptions: { retryPolicyType: StorageRetryPolicyType.EXPONENTIAL, maxTries: 6, tryTimeoutInMs: 10 * 60 * 1000 } }; - const blobExists = await doesAssetExist(blobService, quality, blobName); + const credential = new ClientSecretCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, process.env['AZURE_CLIENT_SECRET']!); + const blobServiceClient = new BlobServiceClient(`https://vscode.blob.core.windows.net`, credential, storagePipelineOptions); + const containerClient = blobServiceClient.getContainerClient(quality); + const blobClient = containerClient.getBlockBlobClient(blobName); + const blobExists = await blobClient.exists(); if (blobExists) { console.log(`Blob ${quality}, ${blobName} already exists, not publishing again.`); return; } - const mooncakeBlobService = azure.createBlobService(storageAccount, process.env['MOONCAKE_STORAGE_ACCESS_KEY']!, `${storageAccount}.blob.core.chinacloudapi.cn`) - .withFilter(new azure.ExponentialRetryPolicyFilter(20)); - - // mooncake is fussy and far away, this is needed! - blobService.defaultClientRequestTimeoutInMs = 10 * 60 * 1000; - mooncakeBlobService.defaultClientRequestTimeoutInMs = 10 * 60 * 1000; + const mooncakeCredential = new ClientSecretCredential(process.env['AZURE_MOONCAKE_TENANT_ID']!, process.env['AZURE_MOONCAKE_CLIENT_ID']!, process.env['AZURE_MOONCAKE_CLIENT_SECRET']!); + const mooncakeBlobServiceClient = new BlobServiceClient(`https://vscode.blob.core.chinacloudapi.cn`, mooncakeCredential, storagePipelineOptions); + const mooncakeContainerClient = mooncakeBlobServiceClient.getContainerClient(quality); + const mooncakeBlobClient = mooncakeContainerClient.getBlockBlobClient(blobName); console.log('Uploading blobs to Azure storage and Mooncake Azure storage...'); + const blobOptions: BlockBlobParallelUploadOptions = { + blobHTTPHeaders: { + blobContentType: mime.lookup(filePath), + blobContentDisposition: `attachment; filename="${fileName}"`, + blobCacheControl: 'max-age=31536000, public' + } + }; + await retry(() => Promise.all([ - uploadBlob(blobService, quality, blobName, filePath, fileName), - uploadBlob(mooncakeBlobService, quality, blobName, filePath, fileName) + blobClient.uploadFile(filePath, blobOptions), + mooncakeBlobClient.uploadFile(filePath, blobOptions) ])); console.log('Blobs successfully uploaded.'); - // TODO: Understand if blobName and blobPath are the same and replace blobPath with blobName if so. const assetUrl = `${process.env['AZURE_CDN_URL']}/${quality}/${blobName}`; - const blobPath = url.parse(assetUrl).path; + const blobPath = new URL(assetUrl).pathname; const mooncakeUrl = `${process.env['MOONCAKE_CDN_URL']}${blobPath}`; const asset: Asset = { @@ -236,7 +226,7 @@ async function main(): Promise { console.log('Asset:', JSON.stringify(asset, null, ' ')); - const client = new CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT']!, key: process.env['AZURE_DOCUMENTDB_MASTERKEY'] }); + const client = new CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT']!, aadCredentials: credential }); const scripts = client.database('builds').container(quality).scripts; await retry(() => scripts.storedProcedure('createAsset').execute('', [commit, asset, true])); diff --git a/build/azure-pipelines/common/createBuild.js b/build/azure-pipelines/common/createBuild.js index d0e950a50f7..cae7a456142 100644 --- a/build/azure-pipelines/common/createBuild.js +++ b/build/azure-pipelines/common/createBuild.js @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); +const identity_1 = require("@azure/identity"); const cosmos_1 = require("@azure/cosmos"); const retry_1 = require("./retry"); if (process.argv.length !== 3) { @@ -38,7 +39,8 @@ async function main() { assets: [], updates: {} }; - const client = new cosmos_1.CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT'], key: process.env['AZURE_DOCUMENTDB_MASTERKEY'] }); + const aadCredentials = new identity_1.ClientSecretCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], process.env['AZURE_CLIENT_SECRET']); + const client = new cosmos_1.CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT'], aadCredentials }); const scripts = client.database('builds').container(quality).scripts; await (0, retry_1.retry)(() => scripts.storedProcedure('createBuild').execute('', [Object.assign(Object.assign({}, build), { _partitionKey: '' })])); } diff --git a/build/azure-pipelines/common/createBuild.ts b/build/azure-pipelines/common/createBuild.ts index e314d7c0988..2877cc86303 100644 --- a/build/azure-pipelines/common/createBuild.ts +++ b/build/azure-pipelines/common/createBuild.ts @@ -5,6 +5,7 @@ 'use strict'; +import { ClientSecretCredential } from '@azure/identity'; import { CosmosClient } from '@azure/cosmos'; import { retry } from './retry'; @@ -47,7 +48,8 @@ async function main(): Promise { updates: {} }; - const client = new CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT']!, key: process.env['AZURE_DOCUMENTDB_MASTERKEY'] }); + const aadCredentials = new ClientSecretCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, process.env['AZURE_CLIENT_SECRET']!); + const client = new CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT']!, aadCredentials }); const scripts = client.database('builds').container(quality).scripts; await retry(() => scripts.storedProcedure('createBuild').execute('', [{ ...build, _partitionKey: '' }])); } diff --git a/build/azure-pipelines/common/releaseBuild.js b/build/azure-pipelines/common/releaseBuild.js index 7aceaa99078..850d4ff2f15 100644 --- a/build/azure-pipelines/common/releaseBuild.js +++ b/build/azure-pipelines/common/releaseBuild.js @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); +const identity_1 = require("@azure/identity"); const cosmos_1 = require("@azure/cosmos"); const retry_1 = require("./retry"); function getEnv(name) { @@ -30,7 +31,8 @@ async function getConfig(client, quality) { async function main() { const commit = getEnv('BUILD_SOURCEVERSION'); const quality = getEnv('VSCODE_QUALITY'); - const client = new cosmos_1.CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT'], key: process.env['AZURE_DOCUMENTDB_MASTERKEY'] }); + const aadCredentials = new identity_1.ClientSecretCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], process.env['AZURE_CLIENT_SECRET']); + const client = new cosmos_1.CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT'], aadCredentials }); const config = await getConfig(client, quality); console.log('Quality config:', config); if (config.frozen) { diff --git a/build/azure-pipelines/common/releaseBuild.ts b/build/azure-pipelines/common/releaseBuild.ts index d42b3f1a078..e798ce490ba 100644 --- a/build/azure-pipelines/common/releaseBuild.ts +++ b/build/azure-pipelines/common/releaseBuild.ts @@ -5,6 +5,7 @@ 'use strict'; +import { ClientSecretCredential } from '@azure/identity'; import { CosmosClient } from '@azure/cosmos'; import { retry } from './retry'; @@ -46,7 +47,8 @@ async function main(): Promise { const commit = getEnv('BUILD_SOURCEVERSION'); const quality = getEnv('VSCODE_QUALITY'); - const client = new CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT']!, key: process.env['AZURE_DOCUMENTDB_MASTERKEY'] }); + const aadCredentials = new ClientSecretCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, process.env['AZURE_CLIENT_SECRET']!); + const client = new CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT']!, aadCredentials }); const config = await getConfig(client, quality); console.log('Quality config:', config); diff --git a/build/azure-pipelines/darwin/app-entitlements.plist b/build/azure-pipelines/darwin/app-entitlements.plist index b43b4b283a1..432c66c1dff 100644 --- a/build/azure-pipelines/darwin/app-entitlements.plist +++ b/build/azure-pipelines/darwin/app-entitlements.plist @@ -8,6 +8,8 @@ com.apple.security.cs.allow-dyld-environment-variables + com.apple.security.cs.disable-library-validation + com.apple.security.device.audio-input com.apple.security.device.camera diff --git a/build/azure-pipelines/darwin/helper-renderer-entitlements.plist b/build/azure-pipelines/darwin/helper-renderer-entitlements.plist index be8b7163da7..4efe1ce508f 100644 --- a/build/azure-pipelines/darwin/helper-renderer-entitlements.plist +++ b/build/azure-pipelines/darwin/helper-renderer-entitlements.plist @@ -4,11 +4,5 @@ com.apple.security.cs.allow-jit - com.apple.security.cs.allow-unsigned-executable-memory - - com.apple.security.cs.disable-library-validation - - com.apple.security.cs.allow-dyld-environment-variables - diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index ce62104f949..f309fc1dab8 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -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: @@ -48,23 +48,23 @@ steps: git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") displayName: Merge distro - - script: | - mkdir -p .build - node build/azure-pipelines/common/computeNodeModulesCacheKey.js $VSCODE_ARCH $ENABLE_TERRAPIN > .build/yarnlockhash - displayName: Prepare yarn cache flags + # - script: | + # mkdir -p .build + # node build/azure-pipelines/common/computeNodeModulesCacheKey.js $VSCODE_ARCH $ENABLE_TERRAPIN > .build/yarnlockhash + # displayName: Prepare yarn cache flags - - task: Cache@2 - inputs: - key: "nodeModules | $(Agent.OS) | .build/yarnlockhash" - path: .build/node_modules_cache - cacheHitVar: NODE_MODULES_RESTORED - displayName: Restore node_modules cache + # - task: Cache@2 + # inputs: + # key: "nodeModules | $(Agent.OS) | .build/yarnlockhash" + # path: .build/node_modules_cache + # cacheHitVar: NODE_MODULES_RESTORED + # displayName: Restore node_modules cache - - script: | - set -e - tar -xzf .build/node_modules_cache/cache.tgz - condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Extract node_modules cache + # - script: | + # set -e + # tar -xzf .build/node_modules_cache/cache.tgz + # condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) + # displayName: Extract node_modules cache - script: | set -e @@ -101,13 +101,13 @@ steps: displayName: Install dependencies condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - script: | - set -e - node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt - mkdir -p .build/node_modules_cache - tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Create node_modules archive + # - script: | + # set -e + # node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt + # mkdir -p .build/node_modules_cache + # tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt + # condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + # displayName: Create node_modules archive # This script brings in the right resources (images, icons, etc) based on the quality (insiders, stable, exploration) - script: | @@ -224,7 +224,7 @@ steps: APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) APP_NAME="`ls $APP_ROOT | head -n 1`" yarn smoketest-no-compile --build "$APP_ROOT/$APP_NAME" --screenshots $(Build.SourcesDirectory)/.build/logs/smoke-tests - timeoutInMinutes: 5 + timeoutInMinutes: 10 displayName: Run smoke tests (Electron) condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) @@ -234,7 +234,7 @@ steps: APP_NAME="`ls $APP_ROOT | head -n 1`" VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin" \ yarn smoketest-no-compile --build "$APP_ROOT/$APP_NAME" --remote --screenshots $(Build.SourcesDirectory)/.build/logs/smoke-tests-remote - timeoutInMinutes: 5 + timeoutInMinutes: 10 displayName: Run smoke tests (Remote) condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) @@ -242,7 +242,7 @@ steps: set -e VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin" \ yarn smoketest-no-compile --web --headless - timeoutInMinutes: 5 + timeoutInMinutes: 10 displayName: Run smoke tests (Browser) 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 diff --git a/build/azure-pipelines/distro-build.yml b/build/azure-pipelines/distro-build.yml index 6031405fe37..53b62b47a4e 100644 --- a/build/azure-pipelines/distro-build.yml +++ b/build/azure-pipelines/distro-build.yml @@ -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 diff --git a/build/azure-pipelines/exploration-build.yml b/build/azure-pipelines/exploration-build.yml index ca78aca17f4..5b6599d8dab 100644 --- a/build/azure-pipelines/exploration-build.yml +++ b/build/azure-pipelines/exploration-build.yml @@ -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 diff --git a/build/azure-pipelines/linux/product-build-alpine.yml b/build/azure-pipelines/linux/product-build-alpine.yml index 2a74a37f5b0..966111fbac5 100644 --- a/build/azure-pipelines/linux/product-build-alpine.yml +++ b/build/azure-pipelines/linux/product-build-alpine.yml @@ -3,16 +3,12 @@ steps: inputs: versionSpec: "14.x" - - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.x" - - task: AzureKeyVault@1 displayName: "Azure Key Vault: Get Secrets" inputs: azureSubscription: "vscode-builds-subscription" KeyVaultName: vscode - SecretsFilter: 'github-distro-mixin-password' + SecretsFilter: "github-distro-mixin-password" - task: DownloadPipelineArtifact@2 inputs: @@ -51,23 +47,23 @@ steps: git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") displayName: Merge distro - - script: | - mkdir -p .build - node build/azure-pipelines/common/computeNodeModulesCacheKey.js "alpine" $ENABLE_TERRAPIN > .build/yarnlockhash - displayName: Prepare yarn cache flags + # - script: | + # mkdir -p .build + # node build/azure-pipelines/common/computeNodeModulesCacheKey.js "alpine" $ENABLE_TERRAPIN > .build/yarnlockhash + # displayName: Prepare yarn cache flags - - task: Cache@2 - inputs: - key: 'nodeModules | $(Agent.OS) | .build/yarnlockhash' - path: .build/node_modules_cache - cacheHitVar: NODE_MODULES_RESTORED - displayName: Restore node_modules cache + # - task: Cache@2 + # inputs: + # key: "nodeModules | $(Agent.OS) | .build/yarnlockhash" + # path: .build/node_modules_cache + # cacheHitVar: NODE_MODULES_RESTORED + # displayName: Restore node_modules cache - - script: | - set -e - tar -xzf .build/node_modules_cache/cache.tgz - condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Extract node_modules cache + # - script: | + # set -e + # tar -xzf .build/node_modules_cache/cache.tgz + # condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) + # displayName: Extract node_modules cache - script: | set -e @@ -93,13 +89,13 @@ steps: displayName: Install dependencies condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - script: | - set -e - node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt - mkdir -p .build/node_modules_cache - tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Create node_modules archive + # - script: | + # set -e + # node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt + # mkdir -p .build/node_modules_cache + # tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt + # condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + # displayName: Create node_modules archive - script: | set -e @@ -107,7 +103,7 @@ steps: displayName: Mix in quality - script: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - displayName: 'Register Docker QEMU' + displayName: "Register Docker QEMU" condition: eq(variables['VSCODE_ARCH'], 'arm64') - script: | diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index a1bcb6a8a90..641be042f73 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -3,16 +3,12 @@ steps: inputs: versionSpec: "14.x" - - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.x" - - task: AzureKeyVault@1 displayName: "Azure Key Vault: Get Secrets" 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: @@ -42,23 +38,23 @@ steps: git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") displayName: Merge distro - - script: | - mkdir -p .build - node build/azure-pipelines/common/computeNodeModulesCacheKey.js $VSCODE_ARCH $ENABLE_TERRAPIN > .build/yarnlockhash - displayName: Prepare yarn cache flags + # - script: | + # mkdir -p .build + # node build/azure-pipelines/common/computeNodeModulesCacheKey.js $VSCODE_ARCH $ENABLE_TERRAPIN > .build/yarnlockhash + # displayName: Prepare yarn cache flags - - task: Cache@2 - inputs: - key: "nodeModules | $(Agent.OS) | .build/yarnlockhash" - path: .build/node_modules_cache - cacheHitVar: NODE_MODULES_RESTORED - displayName: Restore node_modules cache + # - task: Cache@2 + # inputs: + # key: "nodeModules | $(Agent.OS) | .build/yarnlockhash" + # path: .build/node_modules_cache + # cacheHitVar: NODE_MODULES_RESTORED + # displayName: Restore node_modules cache - - script: | - set -e - tar -xzf .build/node_modules_cache/cache.tgz - condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Extract node_modules cache + # - script: | + # set -e + # tar -xzf .build/node_modules_cache/cache.tgz + # condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) + # displayName: Extract node_modules cache - script: | set -e @@ -114,13 +110,13 @@ steps: displayName: Install dependencies condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - script: | - set -e - node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt - mkdir -p .build/node_modules_cache - tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Create node_modules archive + # - script: | + # set -e + # node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt + # mkdir -p .build/node_modules_cache + # tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt + # condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + # displayName: Create node_modules archive - script: | set -e @@ -212,7 +208,7 @@ steps: set -e APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) yarn smoketest-no-compile --build "$APP_PATH" --electronArgs="--disable-dev-shm-usage --use-gl=swiftshader" --screenshots $(Build.SourcesDirectory)/.build/logs/smoke-tests - timeoutInMinutes: 5 + timeoutInMinutes: 10 displayName: Run smoke tests (Electron) condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) @@ -221,7 +217,7 @@ steps: APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ yarn smoketest-no-compile --build "$APP_PATH" --remote --electronArgs="--disable-dev-shm-usage --use-gl=swiftshader" --screenshots $(Build.SourcesDirectory)/.build/logs/smoke-tests-remote - timeoutInMinutes: 5 + timeoutInMinutes: 10 displayName: Run smoke tests (Remote) condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) @@ -229,7 +225,7 @@ steps: set -e VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH)" \ yarn smoketest-no-compile --web --headless --electronArgs="--disable-dev-shm-usage --use-gl=swiftshader" - timeoutInMinutes: 5 + timeoutInMinutes: 10 displayName: Run smoke tests (Browser) condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) @@ -293,9 +289,6 @@ steps: - script: | set -e - AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ - AZURE_STORAGE_ACCESS_KEY_2="$(vscode-storage-key)" \ - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ VSCODE_ARCH="$(VSCODE_ARCH)" \ ./build/azure-pipelines/linux/prepare-publish.sh displayName: Prepare for Publish diff --git a/build/azure-pipelines/linux/snap-build-linux.yml b/build/azure-pipelines/linux/snap-build-linux.yml index a668fec06fb..33a80b74391 100644 --- a/build/azure-pipelines/linux/snap-build-linux.yml +++ b/build/azure-pipelines/linux/snap-build-linux.yml @@ -3,10 +3,6 @@ steps: inputs: versionSpec: "14.x" - - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.x" - - task: DownloadPipelineArtifact@0 displayName: "Download Pipeline Artifact" inputs: @@ -22,6 +18,13 @@ steps: # Make sure we get latest packages sudo apt-get update sudo apt-get upgrade -y + sudo apt-get install -y curl apt-transport-https ca-certificates + + # Yarn + curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - + echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list + sudo apt-get update + sudo apt-get install -y yarn # Define variables REPO="$(pwd)" diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index a67086500af..c0b619cab09 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -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 @@ -152,212 +148,209 @@ stages: - template: product-compile.yml - ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_WINDOWS'], true)) }}: - - stage: Windows - dependsOn: - - Compile - pool: - vmImage: VS2017-Win2016 - jobs: + - stage: Windows + dependsOn: + - Compile + pool: + vmImage: VS2017-Win2016 + jobs: + - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: + - job: Windows + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: x64 + steps: + - template: win32/product-build-win32.yml - - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: - - job: Windows - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: x64 - steps: - - template: win32/product-build-win32.yml + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32_32BIT, true)) }}: + - job: Windows32 + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: ia32 + steps: + - template: win32/product-build-win32.yml - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32_32BIT, true)) }}: - - job: Windows32 - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: ia32 - steps: - - template: win32/product-build-win32.yml - - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: - - job: WindowsARM64 - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: arm64 - steps: - - template: win32/product-build-win32.yml + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: + - job: WindowsARM64 + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: arm64 + steps: + - template: win32/product-build-win32.yml - ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_LINUX'], true)) }}: - - stage: Linux - dependsOn: - - Compile - pool: - vmImage: "Ubuntu-18.04" - jobs: - - - ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}: - - job: Linux - container: vscode-x64 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - DISPLAY: ":10" - steps: - - template: linux/product-build-linux.yml - - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX, true), ne(variables['VSCODE_PUBLISH'], 'false')) }}: - - job: LinuxSnap - dependsOn: - - Linux - container: snapcraft - variables: - VSCODE_ARCH: x64 - steps: - - template: linux/snap-build-linux.yml - - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true)) }}: - - job: LinuxArmhf - container: vscode-armhf - variables: - VSCODE_ARCH: armhf - NPM_ARCH: armv7l - steps: - - template: linux/product-build-linux.yml - - # TODO@joaomoreno: We don't ship ARM snaps for now - - ${{ if and(false, eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true)) }}: - - job: LinuxSnapArmhf - dependsOn: - - LinuxArmhf - container: snapcraft - variables: - VSCODE_ARCH: armhf - steps: - - template: linux/snap-build-linux.yml - - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true)) }}: - - job: LinuxArm64 - container: vscode-arm64 - variables: - VSCODE_ARCH: arm64 - NPM_ARCH: arm64 - steps: - - template: linux/product-build-linux.yml - - # TODO@joaomoreno: We don't ship ARM snaps for now - - ${{ if and(false, eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true)) }}: - - job: LinuxSnapArm64 - dependsOn: - - LinuxArm64 - container: snapcraft - variables: - VSCODE_ARCH: arm64 - steps: - - template: linux/snap-build-linux.yml - - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ALPINE, true)) }}: - - job: LinuxAlpine - variables: - VSCODE_ARCH: x64 - steps: - - template: linux/product-build-alpine.yml - - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ALPINE_ARM64, true)) }}: - - job: LinuxAlpineArm64 - variables: - VSCODE_ARCH: arm64 - steps: - - template: linux/product-build-alpine.yml - - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WEB, true)) }}: - - job: LinuxWeb - variables: - VSCODE_ARCH: x64 - steps: - - template: web/product-build-web.yml - - - ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_MACOS'], true)) }}: - - stage: macOS - dependsOn: - - Compile - pool: - vmImage: macOS-latest - jobs: - - - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: - - job: macOS - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: x64 - steps: - - template: darwin/product-build-darwin.yml - - ${{ if ne(variables['VSCODE_PUBLISH'], 'false') }}: - - job: macOSSign - dependsOn: - - macOS - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: x64 - steps: - - template: darwin/product-build-darwin-sign.yml - - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true)) }}: - - job: macOSARM64 - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: arm64 - steps: - - template: darwin/product-build-darwin.yml - - ${{ if ne(variables['VSCODE_PUBLISH'], 'false') }}: - - job: macOSARM64Sign - dependsOn: - - macOSARM64 - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: arm64 - steps: - - template: darwin/product-build-darwin-sign.yml - - - ${{ if eq(variables['VSCODE_BUILD_MACOS_UNIVERSAL'], true) }}: - - job: macOSUniversal - dependsOn: - - macOS - - macOSARM64 - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: universal - steps: - - template: darwin/product-build-darwin.yml - - ${{ if ne(variables['VSCODE_PUBLISH'], 'false') }}: - - job: macOSUniversalSign - dependsOn: - - macOSUniversal - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: universal - steps: - - template: darwin/product-build-darwin-sign.yml - - - ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), ne(variables['VSCODE_PUBLISH'], 'false')) }}: - - stage: Publish - dependsOn: - - Compile - pool: - vmImage: "Ubuntu-18.04" - variables: - - name: BUILDS_API_URL - value: $(System.CollectionUri)$(System.TeamProject)/_apis/build/builds/$(Build.BuildId)/ - jobs: - - job: PublishBuild - timeoutInMinutes: 180 - displayName: Publish Build - steps: - - template: product-publish.yml - - - ${{ if or(eq(parameters.VSCODE_RELEASE, true), and(in(parameters.VSCODE_QUALITY, 'insider', 'exploration'), eq(variables['VSCODE_SCHEDULEDBUILD'], true))) }}: - - stage: Release + - stage: Linux dependsOn: - - Publish + - Compile pool: vmImage: "Ubuntu-18.04" jobs: - - job: ReleaseBuild - displayName: Release Build + - ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}: + - job: Linux + container: vscode-x64 + variables: + VSCODE_ARCH: x64 + NPM_ARCH: x64 + DISPLAY: ":10" + steps: + - template: linux/product-build-linux.yml + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX, true), ne(variables['VSCODE_PUBLISH'], 'false')) }}: + - job: LinuxSnap + dependsOn: + - Linux + container: snapcraft + variables: + VSCODE_ARCH: x64 + steps: + - template: linux/snap-build-linux.yml + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true)) }}: + - job: LinuxArmhf + container: vscode-armhf + variables: + VSCODE_ARCH: armhf + NPM_ARCH: armv7l + steps: + - template: linux/product-build-linux.yml + + # TODO@joaomoreno: We don't ship ARM snaps for now + - ${{ if and(false, eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true)) }}: + - job: LinuxSnapArmhf + dependsOn: + - LinuxArmhf + container: snapcraft + variables: + VSCODE_ARCH: armhf + steps: + - template: linux/snap-build-linux.yml + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true)) }}: + - job: LinuxArm64 + container: vscode-arm64 + variables: + VSCODE_ARCH: arm64 + NPM_ARCH: arm64 + steps: + - template: linux/product-build-linux.yml + + # TODO@joaomoreno: We don't ship ARM snaps for now + - ${{ if and(false, eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true)) }}: + - job: LinuxSnapArm64 + dependsOn: + - LinuxArm64 + container: snapcraft + variables: + VSCODE_ARCH: arm64 + steps: + - template: linux/snap-build-linux.yml + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ALPINE, true)) }}: + - job: LinuxAlpine + variables: + VSCODE_ARCH: x64 + steps: + - template: linux/product-build-alpine.yml + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ALPINE_ARM64, true)) }}: + - job: LinuxAlpineArm64 + variables: + VSCODE_ARCH: arm64 + steps: + - template: linux/product-build-alpine.yml + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WEB, true)) }}: + - job: LinuxWeb + variables: + VSCODE_ARCH: x64 + steps: + - template: web/product-build-web.yml + + - ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_MACOS'], true)) }}: + - stage: macOS + dependsOn: + - Compile + pool: + vmImage: macOS-latest + jobs: + - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: + - job: macOS + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: x64 + steps: + - template: darwin/product-build-darwin.yml + - ${{ if ne(variables['VSCODE_PUBLISH'], 'false') }}: + - job: macOSSign + dependsOn: + - macOS + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: x64 + steps: + - template: darwin/product-build-darwin-sign.yml + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true)) }}: + - job: macOSARM64 + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: arm64 + steps: + - template: darwin/product-build-darwin.yml + - ${{ if ne(variables['VSCODE_PUBLISH'], 'false') }}: + - job: macOSARM64Sign + dependsOn: + - macOSARM64 + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: arm64 + steps: + - template: darwin/product-build-darwin-sign.yml + + - ${{ if eq(variables['VSCODE_BUILD_MACOS_UNIVERSAL'], true) }}: + - job: macOSUniversal + dependsOn: + - macOS + - macOSARM64 + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: universal + steps: + - template: darwin/product-build-darwin.yml + - ${{ if ne(variables['VSCODE_PUBLISH'], 'false') }}: + - job: macOSUniversalSign + dependsOn: + - macOSUniversal + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: universal + steps: + - template: darwin/product-build-darwin-sign.yml + + - ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), ne(variables['VSCODE_PUBLISH'], 'false')) }}: + - stage: Publish + dependsOn: + - Compile + pool: + vmImage: "Ubuntu-18.04" + variables: + - name: BUILDS_API_URL + value: $(System.CollectionUri)$(System.TeamProject)/_apis/build/builds/$(Build.BuildId)/ + jobs: + - job: PublishBuild + timeoutInMinutes: 180 + displayName: Publish Build steps: - - template: product-release.yml + - template: product-publish.yml + + - ${{ if or(eq(parameters.VSCODE_RELEASE, true), and(in(parameters.VSCODE_QUALITY, 'insider', 'exploration'), eq(variables['VSCODE_SCHEDULEDBUILD'], true))) }}: + - stage: Release + dependsOn: + - Publish + pool: + vmImage: "Ubuntu-18.04" + jobs: + - job: ReleaseBuild + displayName: Release Build + steps: + - template: product-release.yml diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index b8c3fd4140c..88af1af2918 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -3,16 +3,12 @@ steps: inputs: versionSpec: "14.x" - - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.x" - - task: AzureKeyVault@1 displayName: "Azure Key Vault: Get Secrets" inputs: azureSubscription: "vscode-builds-subscription" KeyVaultName: vscode - SecretsFilter: 'github-distro-mixin-password,ticino-storage-key' + SecretsFilter: "github-distro-mixin-password" - script: | set -e @@ -31,24 +27,24 @@ steps: git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") displayName: Merge distro - - script: | - mkdir -p .build - node build/azure-pipelines/common/computeNodeModulesCacheKey.js $VSCODE_ARCH $ENABLE_TERRAPIN > .build/yarnlockhash - displayName: Prepare yarn cache flags + # - script: | + # mkdir -p .build + # node build/azure-pipelines/common/computeNodeModulesCacheKey.js $VSCODE_ARCH $ENABLE_TERRAPIN > .build/yarnlockhash + # displayName: Prepare yarn cache flags - # using `genericNodeModules` instead of `nodeModules` here to avoid sharing the cache with builds running inside containers - - task: Cache@2 - inputs: - key: "genericNodeModules | $(Agent.OS) | .build/yarnlockhash" - path: .build/node_modules_cache - cacheHitVar: NODE_MODULES_RESTORED - displayName: Restore node_modules cache + # # using `genericNodeModules` instead of `nodeModules` here to avoid sharing the cache with builds running inside containers + # - task: Cache@2 + # inputs: + # key: "genericNodeModules | $(Agent.OS) | .build/yarnlockhash" + # path: .build/node_modules_cache + # cacheHitVar: NODE_MODULES_RESTORED + # displayName: Restore node_modules cache - - script: | - set -e - tar -xzf .build/node_modules_cache/cache.tgz - condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Extract node_modules cache + # - script: | + # set -e + # tar -xzf .build/node_modules_cache/cache.tgz + # condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) + # displayName: Extract node_modules cache - script: | set -e @@ -81,13 +77,13 @@ steps: displayName: Install dependencies condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - script: | - set -e - node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt - mkdir -p .build/node_modules_cache - tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Create node_modules archive + # - script: | + # set -e + # node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt + # mkdir -p .build/node_modules_cache + # tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt + # condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + # displayName: Create node_modules archive # Mixin must run before optimize, because the CSS loader will inline small SVGs - script: | @@ -107,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')) diff --git a/build/azure-pipelines/product-publish.yml b/build/azure-pipelines/product-publish.yml index 6a4406f6a26..c43180ea0a3 100644 --- a/build/azure-pipelines/product-publish.yml +++ b/build/azure-pipelines/product-publish.yml @@ -3,16 +3,12 @@ steps: inputs: versionSpec: "12.x" - - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.x" - - task: AzureKeyVault@1 displayName: "Azure Key Vault: Get Secrets" 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 @@ -21,9 +17,31 @@ steps: displayName: Install dependencies - download: current - patterns: '**/artifacts_processed_*.txt' + patterns: "**/artifacts_processed_*.txt" displayName: Download all artifacts_processed text files + - 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" + + - task: AzureCLI@2 + inputs: + azureSubscription: "vscode-builds-mooncake-subscription" + scriptType: pscore + scriptLocation: inlineScript + addSpnToEnvironment: true + inlineScript: | + Write-Host "##vso[task.setvariable variable=AZURE_MOONCAKE_TENANT_ID]$env:tenantId" + Write-Host "##vso[task.setvariable variable=AZURE_MOONCAKE_CLIENT_ID]$env:servicePrincipalId" + Write-Host "##vso[task.setvariable variable=AZURE_MOONCAKE_CLIENT_SECRET;issecret=true]$env:servicePrincipalKey" + - pwsh: | . build/azure-pipelines/win32/exec.ps1 @@ -32,7 +50,9 @@ steps: return } - $env:AZURE_DOCUMENTDB_MASTERKEY = "$(builds-docdb-key-readwrite)" + $env:AZURE_TENANT_ID = "$(AZURE_TENANT_ID)" + $env:AZURE_CLIENT_ID = "$(AZURE_CLIENT_ID)" + $env:AZURE_CLIENT_SECRET = "$(AZURE_CLIENT_SECRET)" $VERSION = node -p "require('./package.json').version" Write-Host "Creating build with version: $VERSION" exec { node build/azure-pipelines/common/createBuild.js $VERSION } @@ -40,10 +60,12 @@ steps: - pwsh: | $env:VSCODE_MIXIN_PASSWORD = "$(github-distro-mixin-password)" - $env:AZURE_DOCUMENTDB_MASTERKEY = "$(builds-docdb-key-readwrite)" - $env:AZURE_STORAGE_ACCESS_KEY = "$(ticino-storage-key)" - $env:AZURE_STORAGE_ACCESS_KEY_2 = "$(vscode-storage-key)" - $env:MOONCAKE_STORAGE_ACCESS_KEY = "$(vscode-mooncake-storage-key)" + $env:AZURE_TENANT_ID = "$(AZURE_TENANT_ID)" + $env:AZURE_CLIENT_ID = "$(AZURE_CLIENT_ID)" + $env:AZURE_CLIENT_SECRET = "$(AZURE_CLIENT_SECRET)" + $env:AZURE_MOONCAKE_TENANT_ID = "$(AZURE_MOONCAKE_TENANT_ID)" + $env:AZURE_MOONCAKE_CLIENT_ID = "$(AZURE_MOONCAKE_CLIENT_ID)" + $env:AZURE_MOONCAKE_CLIENT_SECRET = "$(AZURE_MOONCAKE_CLIENT_SECRET)" build/azure-pipelines/product-publish.ps1 env: SYSTEM_ACCESSTOKEN: $(System.AccessToken) diff --git a/build/azure-pipelines/product-release.yml b/build/azure-pipelines/product-release.yml index d62723be90e..fa6396b1486 100644 --- a/build/azure-pipelines/product-release.yml +++ b/build/azure-pipelines/product-release.yml @@ -3,21 +3,21 @@ steps: inputs: versionSpec: "14.x" - - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.x" - - - task: AzureKeyVault@1 - displayName: "Azure Key Vault: Get Secrets" + - task: AzureCLI@2 inputs: azureSubscription: "vscode-builds-subscription" - KeyVaultName: vscode - SecretsFilter: 'builds-docdb-key-readwrite' + 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 - (cd build ; yarn) - - AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ + AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ + AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ + AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \ node build/azure-pipelines/common/releaseBuild.js diff --git a/build/azure-pipelines/publish-types/publish-types.yml b/build/azure-pipelines/publish-types/publish-types.yml index f557e56279e..043bb5141ba 100644 --- a/build/azure-pipelines/publish-types/publish-types.yml +++ b/build/azure-pipelines/publish-types/publish-types.yml @@ -14,10 +14,6 @@ steps: inputs: versionSpec: "14.x" - - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.x" - - bash: | TAG_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`) CHANNEL="C1C14HJ2F" diff --git a/build/azure-pipelines/sdl-scan.yml b/build/azure-pipelines/sdl-scan.yml index edccd0845b0..d1cd72b3d95 100644 --- a/build/azure-pipelines/sdl-scan.yml +++ b/build/azure-pipelines/sdl-scan.yml @@ -32,209 +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: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.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: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.x" + - script: | + set -e + cat << EOF > ~/.netrc + machine github.com + login vscode + password $(github-distro-mixin-password) + EOF - - task: AzureKeyVault@1 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: "vscode-builds-subscription" - KeyVaultName: vscode - SecretsFilter: "github-distro-mixin-password" + git config user.email "vscode@microsoft.com" + git config user.name "VSCode" + displayName: Prepare tooling - - script: | - set -e - cat << EOF > ~/.netrc - machine github.com - login vscode - password $(github-distro-mixin-password) - EOF + - script: | + set -e + git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") + displayName: Merge distro - git config user.email "vscode@microsoft.com" - git config user.name "VSCode" - displayName: Prepare tooling + - 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 - git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") - displayName: Merge distro + - script: | + set -e + yarn --cwd build + yarn --cwd build compile + displayName: Compile build tools - - 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 + export npm_config_arch=$(NPM_ARCH) - - script: | - set -e - yarn --cwd build - yarn --cwd build compile - displayName: Compile build tools + 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 - - script: | - set -e - export npm_config_arch=$(NPM_ARCH) + if [ "$VSCODE_ARCH" == "x64" ]; then + export VSCODE_REMOTE_CC=$(which gcc-4.8) + export VSCODE_REMOTE_CXX=$(which g++-4.8) + 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 + 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 - if [ "$VSCODE_ARCH" == "x64" ]; then - export VSCODE_REMOTE_CC=$(which gcc-4.8) - export VSCODE_REMOTE_CXX=$(which g++-4.8) - fi + - script: | + set -e + yarn gulp vscode-symbols-linux-$(VSCODE_ARCH) + displayName: Build - 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 + - 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 - - 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: TSAUpload@2 - inputs: - GdnPublishTsaConfigFile: '$(Build.SourceDirectory)\build\azure-pipelines\.gdntsa' + - task: TSAUpload@2 + inputs: + GdnPublishTsaConfigFile: '$(Build.SourceDirectory)\build\azure-pipelines\.gdntsa' diff --git a/build/azure-pipelines/upload-cdn.js b/build/azure-pipelines/upload-cdn.js index 16a072905a0..fe3817c9183 100644 --- a/build/azure-pipelines/upload-cdn.js +++ b/build/azure-pipelines/upload-cdn.js @@ -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); +}); diff --git a/build/azure-pipelines/upload-cdn.ts b/build/azure-pipelines/upload-cdn.ts index 71589033867..c35582017d7 100644 --- a/build/azure-pipelines/upload-cdn.ts +++ b/build/azure-pipelines/upload-cdn.ts @@ -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 { + 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); +}); diff --git a/build/azure-pipelines/upload-configuration.js b/build/azure-pipelines/upload-configuration.js new file mode 100644 index 00000000000..689d99fdae0 --- /dev/null +++ b/build/azure-pipelines/upload-configuration.js @@ -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()); + // + // 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); + }); +} diff --git a/build/azure-pipelines/upload-configuration.ts b/build/azure-pipelines/upload-configuration.ts new file mode 100644 index 00000000000..3acc337e749 --- /dev/null +++ b/build/azure-pipelines/upload-configuration.ts @@ -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 { + 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()); + + // + // 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 { + 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); + }); +} diff --git a/build/azure-pipelines/upload-nlsmetadata.js b/build/azure-pipelines/upload-nlsmetadata.js index 27c9438187f..a09d569f7f6 100644 --- a/build/azure-pipelines/upload-nlsmetadata.js +++ b/build/azure-pipelines/upload-nlsmetadata.js @@ -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); +}); diff --git a/build/azure-pipelines/upload-nlsmetadata.ts b/build/azure-pipelines/upload-nlsmetadata.ts index 72a6701dddd..b3a19b218e3 100644 --- a/build/azure-pipelines/upload-nlsmetadata.ts +++ b/build/azure-pipelines/upload-nlsmetadata.ts @@ -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 { + 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); +}); + diff --git a/build/azure-pipelines/upload-sourcemaps.js b/build/azure-pipelines/upload-sourcemaps.js index b2a886f6c7c..4edcd2ccd70 100644 --- a/build/azure-pipelines/upload-sourcemaps.js +++ b/build/azure-pipelines/upload-sourcemaps.js @@ -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); +}); diff --git a/build/azure-pipelines/upload-sourcemaps.ts b/build/azure-pipelines/upload-sourcemaps.ts index 769e224e6f7..f065ff2cf38 100644 --- a/build/azure-pipelines/upload-sourcemaps.ts +++ b/build/azure-pipelines/upload-sourcemaps.ts @@ -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 { + 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); +}); + diff --git a/build/azure-pipelines/web/product-build-web.yml b/build/azure-pipelines/web/product-build-web.yml index 4977207b896..2a467124141 100644 --- a/build/azure-pipelines/web/product-build-web.yml +++ b/build/azure-pipelines/web/product-build-web.yml @@ -3,16 +3,12 @@ steps: inputs: versionSpec: "14.x" - - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.x" - - task: AzureKeyVault@1 displayName: "Azure Key Vault: Get Secrets" 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: @@ -42,23 +38,23 @@ steps: git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") displayName: Merge distro - - script: | - mkdir -p .build - node build/azure-pipelines/common/computeNodeModulesCacheKey.js "web" $ENABLE_TERRAPIN > .build/yarnlockhash - displayName: Prepare yarn cache flags + # - script: | + # mkdir -p .build + # node build/azure-pipelines/common/computeNodeModulesCacheKey.js "web" $ENABLE_TERRAPIN > .build/yarnlockhash + # displayName: Prepare yarn cache flags - - task: Cache@2 - inputs: - key: 'nodeModules | $(Agent.OS) | .build/yarnlockhash' - path: .build/node_modules_cache - cacheHitVar: NODE_MODULES_RESTORED - displayName: Restore node_modules cache + # - task: Cache@2 + # inputs: + # key: "nodeModules | $(Agent.OS) | .build/yarnlockhash" + # path: .build/node_modules_cache + # cacheHitVar: NODE_MODULES_RESTORED + # displayName: Restore node_modules cache - - script: | - set -e - tar -xzf .build/node_modules_cache/cache.tgz - condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Extract node_modules cache + # - script: | + # set -e + # tar -xzf .build/node_modules_cache/cache.tgz + # condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) + # displayName: Extract node_modules cache - script: | set -e @@ -84,13 +80,13 @@ steps: displayName: Install dependencies condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - script: | - set -e - node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt - mkdir -p .build/node_modules_cache - tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Create node_modules archive + # - script: | + # set -e + # node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt + # mkdir -p .build/node_modules_cache + # tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt + # condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + # displayName: Create node_modules archive - script: | set -e @@ -103,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 @@ -115,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')) diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index ccd863c615b..6ff2da9724a 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -3,10 +3,6 @@ steps: inputs: versionSpec: "14.x" - - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.x" - - task: UsePythonVersion@0 inputs: versionSpec: "2.x" @@ -17,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: @@ -46,25 +42,25 @@ steps: exec { git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") } displayName: Merge distro - - powershell: | - "$(VSCODE_ARCH)" | Out-File -Encoding ascii -NoNewLine .build\arch - "$env:ENABLE_TERRAPIN" | Out-File -Encoding ascii -NoNewLine .build\terrapin - node build/azure-pipelines/common/computeNodeModulesCacheKey.js > .build/yarnlockhash - displayName: Prepare yarn cache flags + # - powershell: | + # "$(VSCODE_ARCH)" | Out-File -Encoding ascii -NoNewLine .build\arch + # "$env:ENABLE_TERRAPIN" | Out-File -Encoding ascii -NoNewLine .build\terrapin + # node build/azure-pipelines/common/computeNodeModulesCacheKey.js > .build/yarnlockhash + # displayName: Prepare yarn cache flags - - task: Cache@2 - inputs: - key: "nodeModules | $(Agent.OS) | .build/arch, .build/terrapin, .build/yarnlockhash" - path: .build/node_modules_cache - cacheHitVar: NODE_MODULES_RESTORED - displayName: Restore node_modules cache + # - task: Cache@2 + # inputs: + # key: "nodeModules | $(Agent.OS) | .build/arch, .build/terrapin, .build/yarnlockhash" + # path: .build/node_modules_cache + # cacheHitVar: NODE_MODULES_RESTORED + # displayName: Restore node_modules cache - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { 7z.exe x .build/node_modules_cache/cache.7z -aos } - condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Extract node_modules cache + # - powershell: | + # . build/azure-pipelines/win32/exec.ps1 + # $ErrorActionPreference = "Stop" + # exec { 7z.exe x .build/node_modules_cache/cache.7z -aos } + # condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) + # displayName: Extract node_modules cache - powershell: | . build/azure-pipelines/win32/exec.ps1 @@ -88,14 +84,14 @@ steps: displayName: Install dependencies condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt } - exec { mkdir -Force .build/node_modules_cache } - exec { 7z.exe a .build/node_modules_cache/cache.7z -mx3 `@.build/node_modules_list.txt } - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Create node_modules archive + # - powershell: | + # . build/azure-pipelines/win32/exec.ps1 + # $ErrorActionPreference = "Stop" + # exec { node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt } + # exec { mkdir -Force .build/node_modules_cache } + # exec { 7z.exe a .build/node_modules_cache/cache.7z -mx3 `@.build/node_modules_list.txt } + # condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + # displayName: Create node_modules archive - powershell: | . build/azure-pipelines/win32/exec.ps1 @@ -208,7 +204,7 @@ steps: $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" exec { yarn smoketest-no-compile --build "$AppRoot" --screenshots $(Build.SourcesDirectory)\.build\logs\smoke-tests } displayName: Run smoke tests (Electron) - timeoutInMinutes: 5 + timeoutInMinutes: 10 condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) - powershell: | @@ -218,7 +214,7 @@ steps: $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)" exec { yarn smoketest-no-compile --build "$AppRoot" --remote --screenshots $(Build.SourcesDirectory)\.build\logs\smoke-tests-remote } displayName: Run smoke tests (Remote) - timeoutInMinutes: 5 + timeoutInMinutes: 10 condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) - powershell: | @@ -227,7 +223,7 @@ steps: $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH)" exec { yarn smoketest-no-compile --web --browser firefox --headless } displayName: Run smoke tests (Browser) - timeoutInMinutes: 5 + timeoutInMinutes: 10 condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) - task: PublishPipelineArtifact@0 @@ -310,9 +306,6 @@ steps: - powershell: | . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" - $env:AZURE_STORAGE_ACCESS_KEY_2 = "$(vscode-storage-key)" - $env:AZURE_DOCUMENTDB_MASTERKEY = "$(builds-docdb-key-readwrite)" - $env:VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" .\build\azure-pipelines\win32\prepare-publish.ps1 displayName: Publish condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false')) diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index e292abeda2c..250eb01bcdd 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -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()); - - // - // 1.25.1, 1,234,567 commits, main = 1250112345671 - return util.versionStringToNumber(packageJson.version) * 1e8 + count * 10 + branchId; - } catch (e) { - throw new Error('Could not determine build number: ' + e.toString()); - } -} diff --git a/build/lib/compilation.js b/build/lib/compilation.js index 5a882c3f88a..b81e714fa41 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -183,7 +183,7 @@ function apiProposalNamesGenerator() { try { const t1 = Date.now(); const proposalNames = []; - for (let file of fs.readdirSync(dtsFolder)) { + for (let file of fs.readdirSync(dtsFolder).sort()) { const match = pattern.exec(file); if (match) { proposalNames.push([match[1], `https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/${file}`]); diff --git a/build/lib/compilation.ts b/build/lib/compilation.ts index 120c0403755..892acaa4216 100644 --- a/build/lib/compilation.ts +++ b/build/lib/compilation.ts @@ -224,7 +224,7 @@ function apiProposalNamesGenerator() { const t1 = Date.now(); const proposalNames: [name: string, url: string][] = []; - for (let file of fs.readdirSync(dtsFolder)) { + for (let file of fs.readdirSync(dtsFolder).sort()) { const match = pattern.exec(file); if (match) { proposalNames.push([match[1], `https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/${file}`]); diff --git a/build/package.json b/build/package.json index 337cac1406a..7b1131e894c 100644 --- a/build/package.json +++ b/build/package.json @@ -3,14 +3,15 @@ "version": "1.0.0", "license": "MIT", "devDependencies": { - "@azure/cosmos": "^3.9.3", - "@azure/storage-blob": "^12.4.0", + "@azure/cosmos": "^3.14.1", + "@azure/identity": "^2.0.1", + "@azure/storage-blob": "^12.8.0", "@electron/get": "^1.12.4", "@types/ansi-colors": "^3.2.0", - "@types/azure": "0.9.19", "@types/byline": "^4.2.32", "@types/cssnano": "^4.0.0", "@types/debounce": "^1.0.0", + "@types/debug": "4.1.5", "@types/eslint": "4.16.1", "@types/fancy-log": "^1.3.0", "@types/fs-extra": "^9.0.12", @@ -43,7 +44,6 @@ "@typescript-eslint/experimental-utils": "~2.13.0", "@typescript-eslint/parser": "^3.3.0", "applicationinsights": "1.0.8", - "azure-storage": "^2.1.0", "byline": "^5.0.0", "colors": "^1.4.0", "commander": "^7.0.0", @@ -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", @@ -62,7 +62,7 @@ "plist": "^3.0.1", "source-map": "0.6.1", "tmp": "^0.2.1", - "typescript": "^4.6.0-dev.20211108", + "typescript": "^4.6.0-dev.20211115", "vsce": "^1.100.0", "vscode-universal-bundler": "^0.0.2" }, diff --git a/build/yarn.lock b/build/yarn.lock index c2ca84ea946..bf575cab8d4 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -14,44 +14,56 @@ resolved "https://registry.yarnpkg.com/@azure/core-asynciterator-polyfill/-/core-asynciterator-polyfill-1.0.0.tgz#dcccebb88406e5c76e0e1d52e8cc4c43a68b3ee7" integrity sha512-kmv8CGrPfN9SwMwrkiBK9VTQYxdFQEGe0BmQk+M8io56P9KNzpAxcWE/1fxJj7uouwN4kXF0BHW8DNlgx+wtCg== -"@azure/core-auth@^1.1.3": - version "1.1.4" - resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.1.4.tgz#af9a334acf3cb9c49e6013e6caf6dc9d43476030" - integrity sha512-+j1embyH1jqf04AIfJPdLafd5SC1y6z1Jz4i+USR1XkTp6KM8P5u4/AjmWMVoEQdM/M29PJcRDZcCEWjK9S1bw== +"@azure/core-auth@^1.3.0": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.3.2.tgz#6a2c248576c26df365f6c7881ca04b7f6d08e3d0" + integrity sha512-7CU6DmCHIZp5ZPiZ9r3J17lTKMmYsm/zGvNkjArQwPkrLlZ1TZ+EUYfGgh2X31OLMVAQCTJZW4cXHJi02EbJnA== dependencies: "@azure/abort-controller" "^1.0.0" - tslib "^2.0.0" + tslib "^2.2.0" -"@azure/core-http@^1.2.0": - version "1.2.2" - resolved "https://registry.yarnpkg.com/@azure/core-http/-/core-http-1.2.2.tgz#a6f7717184fd2657d3acabd1d64dfdc0bd531ce3" - integrity sha512-9eu2OcbR7e44gqBy4U1Uv8NTWgLIMwKXMEGgO2MahsJy5rdTiAhs5fJHQffPq8uX2MFh21iBODwO9R/Xlov88A== +"@azure/core-client@^1.0.0": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@azure/core-client/-/core-client-1.3.2.tgz#c0e999a069e9ea16b4b2c623a24d5443f67c6bfa" + integrity sha512-qfkRYKmeEmisluMdGTbBtXeyBLaImjFeVW0gcT5yRAwxJmlnTvSyD+a3PjukAtjIrl/tnb4WSJOBpONSJ91+5Q== dependencies: "@azure/abort-controller" "^1.0.0" - "@azure/core-auth" "^1.1.3" - "@azure/core-tracing" "1.0.0-preview.9" + "@azure/core-asynciterator-polyfill" "^1.0.0" + "@azure/core-auth" "^1.3.0" + "@azure/core-rest-pipeline" "^1.1.0" + "@azure/core-tracing" "1.0.0-preview.13" + tslib "^2.2.0" + +"@azure/core-http@^2.0.0": + version "2.2.2" + resolved "https://registry.yarnpkg.com/@azure/core-http/-/core-http-2.2.2.tgz#573798f087d808d39aa71fd7c52b8d7b89f440da" + integrity sha512-V1DdoO9V/sFimKpdWoNBgsE+QUjQgpXYnxrTdUp5RyhsTJjvEVn/HKmTQXIHuLUUo6IyIWj+B+Dg4VaXse9dIA== + dependencies: + "@azure/abort-controller" "^1.0.0" + "@azure/core-asynciterator-polyfill" "^1.0.0" + "@azure/core-auth" "^1.3.0" + "@azure/core-tracing" "1.0.0-preview.13" "@azure/logger" "^1.0.0" - "@opentelemetry/api" "^0.10.2" "@types/node-fetch" "^2.5.0" - "@types/tunnel" "^0.0.1" - form-data "^3.0.0" + "@types/tunnel" "^0.0.3" + form-data "^4.0.0" node-fetch "^2.6.0" process "^0.11.10" tough-cookie "^4.0.0" - tslib "^2.0.0" + tslib "^2.2.0" tunnel "^0.0.6" uuid "^8.3.0" xml2js "^0.4.19" -"@azure/core-lro@^1.0.2": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@azure/core-lro/-/core-lro-1.0.3.tgz#1ddfb4ecdb81ce87b5f5d972ffe2acbbc46e524e" - integrity sha512-Py2crJ84qx1rXkzIwfKw5Ni4WJuzVU7KAF6i1yP3ce8fbynUeu8eEWS4JGtSQgU7xv02G55iPDROifmSDbxeHA== +"@azure/core-lro@^2.2.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@azure/core-lro/-/core-lro-2.2.1.tgz#5527b41037c658d3aefc19d68633e51e53d6e6a3" + integrity sha512-HE6PBl+mlKa0eBsLwusHqAqjLc5n9ByxeDo3Hz4kF3B1hqHvRkBr4oMgoT6tX7Hc3q97KfDctDUon7EhvoeHPA== dependencies: "@azure/abort-controller" "^1.0.0" - "@azure/core-http" "^1.2.0" - events "^3.0.0" - tslib "^2.0.0" + "@azure/core-tracing" "1.0.0-preview.13" + "@azure/logger" "^1.0.0" + tslib "^2.2.0" "@azure/core-paging@^1.1.1": version "1.1.3" @@ -60,32 +72,76 @@ dependencies: "@azure/core-asynciterator-polyfill" "^1.0.0" -"@azure/core-tracing@1.0.0-preview.9": - version "1.0.0-preview.9" - resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.0.0-preview.9.tgz#84f3b85572013f9d9b85e1e5d89787aa180787eb" - integrity sha512-zczolCLJ5QG42AEPQ+Qg9SRYNUyB+yZ5dzof4YEc+dyWczO9G2sBqbAjLB7IqrsdHN2apkiB2oXeDKCsq48jug== +"@azure/core-rest-pipeline@^1.1.0", "@azure/core-rest-pipeline@^1.2.0": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.3.2.tgz#82bfb4e960b4ecf4f1a1cdb1afde4ce9192aef09" + integrity sha512-kymICKESeHBpVLgQiAxllgWdSTopkqtmfPac8ITwMCxNEC6hzbSpqApYbjzxbBNkBMgoD4GESo6LLhR/sPh6kA== + dependencies: + "@azure/abort-controller" "^1.0.0" + "@azure/core-auth" "^1.3.0" + "@azure/core-tracing" "1.0.0-preview.13" + "@azure/logger" "^1.0.0" + form-data "^4.0.0" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + tslib "^2.2.0" + uuid "^8.3.0" + +"@azure/core-tracing@1.0.0-preview.13": + version "1.0.0-preview.13" + resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz#55883d40ae2042f6f1e12b17dd0c0d34c536d644" + integrity sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ== + dependencies: + "@opentelemetry/api" "^1.0.1" + tslib "^2.2.0" + +"@azure/core-util@^1.0.0-beta.1": + version "1.0.0-beta.1" + resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.0.0-beta.1.tgz#2efd2c74b4b0a38180369f50fe274a3c4cd36e98" + integrity sha512-pS6cup979/qyuyNP9chIybK2qVkJ3MarbY/bx3JcGKE6An6dRweLnsfJfU2ydqUI/B51Rjnn59ajHIhCUTwWZw== dependencies: - "@opencensus/web-types" "0.0.7" - "@opentelemetry/api" "^0.10.2" tslib "^2.0.0" -"@azure/cosmos@^3.9.3": - version "3.9.3" - resolved "https://registry.yarnpkg.com/@azure/cosmos/-/cosmos-3.9.3.tgz#7e95ff92e5c3e9da7e8316bc50c9cc928be6c1d6" - integrity sha512-1mh8a6LAIykz24tJvQpafXiABUfq+HSAZBFJVZXea0Rd0qG8Ia9z8AK9FtPbC1nPvDC2RID2mRIjJvYbxRM/BA== +"@azure/cosmos@^3.14.1": + version "3.14.1" + resolved "https://registry.yarnpkg.com/@azure/cosmos/-/cosmos-3.14.1.tgz#974087eca9a76f9826d14898414219f19474f314" + integrity sha512-i8HJOlmVfr1P5qMNgKbEpszddtT8Ooskj2pJm0ZD7jeMuwN2tYlQ68eLyCmynv1j5PQimLGjlobBVuFuM8uNAA== dependencies: - "@types/debug" "^4.1.4" + "@azure/core-auth" "^1.3.0" + "@azure/core-rest-pipeline" "^1.2.0" debug "^4.1.1" - fast-json-stable-stringify "^2.0.0" + fast-json-stable-stringify "^2.1.0" jsbi "^3.1.3" - node-abort-controller "^1.0.4" - node-fetch "^2.6.0" + node-abort-controller "^1.2.0" priorityqueuejs "^1.0.0" semaphore "^1.0.5" - tslib "^2.0.0" + tslib "^2.2.0" universal-user-agent "^6.0.0" uuid "^8.3.0" +"@azure/identity@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@azure/identity/-/identity-2.0.1.tgz#31107506371e520bc874647a9e4384cfd2f85103" + integrity sha512-gdGGuLKlKIQaf2RefA84keoBfmWfiAntbW2SzcdKvwLSGzsio/qkyY3sYUpXRz/sqLDxguuimgZukp7TPgwIlg== + dependencies: + "@azure/abort-controller" "^1.0.0" + "@azure/core-auth" "^1.3.0" + "@azure/core-client" "^1.0.0" + "@azure/core-rest-pipeline" "^1.1.0" + "@azure/core-tracing" "1.0.0-preview.13" + "@azure/core-util" "^1.0.0-beta.1" + "@azure/logger" "^1.0.0" + "@azure/msal-browser" "^2.16.0" + "@azure/msal-common" "^4.5.1" + "@azure/msal-node" "^1.3.0" + "@types/stoppable" "^1.1.0" + events "^3.0.0" + jws "^4.0.0" + open "^8.0.0" + stoppable "^1.1.0" + tslib "^2.2.0" + uuid "^8.3.0" + "@azure/logger@^1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.0.1.tgz#19b333203d1b2931353d8879e814b64a7274837a" @@ -93,20 +149,50 @@ dependencies: tslib "^2.0.0" -"@azure/storage-blob@^12.4.0": - version "12.4.0" - resolved "https://registry.yarnpkg.com/@azure/storage-blob/-/storage-blob-12.4.0.tgz#7127ddd9f413105e2c3688691bc4c6245d0806b3" - integrity sha512-OnhVSoKD1HzBB79/rFzPbC4w9TdzFXeoOwkX+aIu3rb8qvN0VaqvUqZXSrBCyG2LcLyVkY4MPCJQBrmEUm9kvw== +"@azure/msal-browser@^2.16.0": + version "2.19.0" + resolved "https://registry.yarnpkg.com/@azure/msal-browser/-/msal-browser-2.19.0.tgz#6915d200e0679eb8b26368bf0e0fc02ee4a8617a" + integrity sha512-nVMMSbFeocGv3SUYGBD+3pkE/pbAciGhER3KCjsBu6Sy9EDaBCiQ418KZfHBcCcrNQgFxf3nleWdeYoYX7281g== + dependencies: + "@azure/msal-common" "^5.1.0" + +"@azure/msal-common@^4.5.1": + version "4.5.1" + resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-4.5.1.tgz#f35af8b634ae24aebd0906deb237c0db1afa5826" + integrity sha512-/i5dXM+QAtO+6atYd5oHGBAx48EGSISkXNXViheliOQe+SIFMDo3gSq3lL54W0suOSAsVPws3XnTaIHlla0PIQ== + dependencies: + debug "^4.1.1" + +"@azure/msal-common@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-5.1.0.tgz#e3a75a8ba1602da040698046161961d6dc59bbc4" + integrity sha512-4zHZ5Ec7jAgTIWZO3ap1ozgIPGAirF1wL8UhsmPF9QDoZz0cMHdaNmtov5i2+6Xq37YMzhN5s50EFHBuXd7sDQ== + dependencies: + debug "^4.1.1" + +"@azure/msal-node@^1.3.0": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@azure/msal-node/-/msal-node-1.3.3.tgz#3b977fa371d0c5fcd63df2e458353044a1eb64a1" + integrity sha512-ZtVCVzr7V4xEeqICa7E9g6BY3noZv96XG11ENuqEiz/PA1OzPD1/x0QF6BPHVldST8wwoevXxPw+t/h3AFII7w== + dependencies: + "@azure/msal-common" "^5.1.0" + axios "^0.21.4" + jsonwebtoken "^8.5.1" + uuid "^8.3.0" + +"@azure/storage-blob@^12.8.0": + version "12.8.0" + resolved "https://registry.yarnpkg.com/@azure/storage-blob/-/storage-blob-12.8.0.tgz#97b7ecc6c7b17bcbaf0281c79c16af6f512d6130" + integrity sha512-c8+Wz19xauW0bGkTCoqZH4dYfbtBniPiGiRQOn1ca6G5jsjr4azwaTk9gwjVY8r3vY2Taf95eivLzipfIfiS4A== dependencies: "@azure/abort-controller" "^1.0.0" - "@azure/core-http" "^1.2.0" - "@azure/core-lro" "^1.0.2" + "@azure/core-http" "^2.0.0" + "@azure/core-lro" "^2.2.0" "@azure/core-paging" "^1.1.1" - "@azure/core-tracing" "1.0.0-preview.9" + "@azure/core-tracing" "1.0.0-preview.13" "@azure/logger" "^1.0.0" - "@opentelemetry/api" "^0.10.2" events "^3.0.0" - tslib "^2.0.0" + tslib "^2.2.0" "@electron/get@^1.12.4": version "1.12.4" @@ -131,22 +217,10 @@ dependencies: cross-spawn "^7.0.1" -"@opencensus/web-types@0.0.7": - version "0.0.7" - resolved "https://registry.yarnpkg.com/@opencensus/web-types/-/web-types-0.0.7.tgz#4426de1fe5aa8f624db395d2152b902874f0570a" - integrity sha512-xB+w7ZDAu3YBzqH44rCmG9/RlrOmFuDPt/bpf17eJr8eZSrLt7nc7LnWdxM9Mmoj/YKMHpxRg28txu3TcpiL+g== - -"@opentelemetry/api@^0.10.2": - version "0.10.2" - resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-0.10.2.tgz#9647b881f3e1654089ff7ea59d587b2d35060654" - integrity sha512-GtpMGd6vkzDMYcpu2t9LlhEgMy/SzBwRnz48EejlRArYqZzqSzAsKmegUK7zHgl+EOIaK9mKHhnRaQu3qw20cA== - dependencies: - "@opentelemetry/context-base" "^0.10.2" - -"@opentelemetry/context-base@^0.10.2": - version "0.10.2" - resolved "https://registry.yarnpkg.com/@opentelemetry/context-base/-/context-base-0.10.2.tgz#55bea904b2b91aa8a8675df9eaba5961bddb1def" - integrity sha512-hZNKjKOYsckoOEgBziGMnBcX0M7EtstnCmwz5jZUOUYwlZ+/xxX6z3jPu1XVO2Jivk0eLfuP9GP+vFD49CMetw== +"@opentelemetry/api@^1.0.1": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.0.3.tgz#13a12ae9e05c2a782f7b5e84c3cbfda4225eaf80" + integrity sha512-puWxACExDe9nxbBB3lOymQFrLYml2dVOrd7USiVRnSbgXE+KwBu+HxFvxrzfqsiSda9IWsXJG1ef7C1O2/GmKQ== "@sindresorhus/is@^0.14.0": version "0.14.0" @@ -172,18 +246,16 @@ dependencies: defer-to-connect "^2.0.0" +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + "@types/ansi-colors@^3.2.0": version "3.2.0" resolved "https://registry.yarnpkg.com/@types/ansi-colors/-/ansi-colors-3.2.0.tgz#3e4fe85d9131ce1c6994f3040bd0b25306c16a6e" integrity sha512-0caWAhXht9N2lOdMzJLXybsSkYCx1QOdxx6pae48tswI9QV3DFX26AoOpy0JxwhCb+zISTqmd6H8t9Zby9BoZg== -"@types/azure@0.9.19": - version "0.9.19" - resolved "https://registry.yarnpkg.com/@types/azure/-/azure-0.9.19.tgz#1a6a9bd856b437ddecf3f9fc8407a683c869ba02" - integrity sha1-Gmqb2Fa0N93s8/n8hAemg8hpugI= - dependencies: - "@types/node" "*" - "@types/byline@^4.2.32": version "4.2.32" resolved "https://registry.yarnpkg.com/@types/byline/-/byline-4.2.32.tgz#9d35ec15968056118548412ee24c2c3026c997dc" @@ -226,7 +298,7 @@ resolved "https://registry.yarnpkg.com/@types/debounce/-/debounce-1.0.0.tgz#417560200331e1bb84d72da85391102c2fcd61b7" integrity sha1-QXVgIAMx4buE1y2oU5EQLC/NYbc= -"@types/debug@^4.1.4": +"@types/debug@4.1.5": version "4.1.5" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd" integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ== @@ -471,6 +543,13 @@ resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== +"@types/stoppable@^1.1.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/stoppable/-/stoppable-1.1.1.tgz#a6f1f280e29f8f3c743277534425e0a75041d2f9" + integrity sha512-b8N+fCADRIYYrGZOcmOR8ZNBOqhktWTB/bMUl5LvGtT201QKJZOOH5UsFyI3qtteM6ZAJbJqZoBcLqqxKIwjhw== + dependencies: + "@types/node" "*" + "@types/tapable@^1": version "1.0.8" resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310" @@ -500,10 +579,10 @@ resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.2.tgz#e0d481d8bb282ad8a8c9e100ceb72c995fb5e709" integrity sha512-vOVmaruQG5EatOU/jM6yU2uCp3Lz6mK1P5Ztu4iJjfM4SVHU9XYktPUQtKlIXuahqXHdEyUarMrBEwg5Cwu+bA== -"@types/tunnel@^0.0.1": - version "0.0.1" - resolved "https://registry.yarnpkg.com/@types/tunnel/-/tunnel-0.0.1.tgz#0d72774768b73df26f25df9184273a42da72b19c" - integrity sha512-AOqu6bQu5MSWwYvehMXLukFHnupHrpZ8nvgae5Ggie9UwzDR1CCwoXgSSWNZJuyOlCdfdsWMA5F2LlmvyoTv8A== +"@types/tunnel@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@types/tunnel/-/tunnel-0.0.3.tgz#f109e730b072b3136347561fc558c9358bb8c6e9" + integrity sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA== dependencies: "@types/node" "*" @@ -652,15 +731,12 @@ dependencies: eslint-visitor-keys "^1.1.0" -ajv@^6.12.3: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" + debug "4" ansi-colors@^1.0.1: version "1.1.0" @@ -727,18 +803,6 @@ asar@^3.0.3: optionalDependencies: "@types/glob" "^7.1.1" -asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= - assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" @@ -754,15 +818,12 @@ at-least-node@^1.0.0: resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= - -aws4@^1.8.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" - integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== +axios@^0.21.4: + version "0.21.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== + dependencies: + follow-redirects "^1.14.0" azure-devops-node-api@^11.0.1: version "11.0.1" @@ -772,23 +833,6 @@ azure-devops-node-api@^11.0.1: tunnel "0.0.6" typed-rest-client "^1.8.4" -azure-storage@^2.1.0: - version "2.10.3" - resolved "https://registry.yarnpkg.com/azure-storage/-/azure-storage-2.10.3.tgz#c5966bf929d87587d78f6847040ea9a4b1d4a50a" - integrity sha512-IGLs5Xj6kO8Ii90KerQrrwuJKexLgSwYC4oLWmc11mzKe7Jt2E5IVg+ZQ8K53YWZACtVTMBNO3iGuA+4ipjJxQ== - dependencies: - browserify-mime "~1.2.9" - extend "^3.0.2" - json-edm-parser "0.1.2" - md5.js "1.3.4" - readable-stream "~2.0.0" - request "^2.86.0" - underscore "~1.8.3" - uuid "^3.0.0" - validator "~9.4.1" - xml2js "0.2.8" - xmlbuilder "^9.0.7" - balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -799,13 +843,6 @@ base64-js@^1.2.3: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= - dependencies: - tweetnacl "^0.14.3" - bluebird@^3.5.0: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" @@ -829,11 +866,6 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -browserify-mime@~1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/browserify-mime/-/browserify-mime-1.2.9.tgz#aeb1af28de6c0d7a6a2ce40adb68ff18422af31f" - integrity sha1-rrGvKN5sDXpqLOQK22j/GEIq8x8= - buffer-alloc-unsafe@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" @@ -852,6 +884,11 @@ buffer-crc32@~0.2.3: resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= + buffer-equal@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" @@ -906,11 +943,6 @@ call-bind@^1.0.0: function-bind "^1.1.1" get-intrinsic "^1.0.2" -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= - chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -1002,7 +1034,7 @@ colors@^1.4.0: resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== -combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: +combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -1054,7 +1086,7 @@ core-js@^3.6.5: resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.15.2.tgz#740660d2ff55ef34ce664d7e2455119c5bdd3d61" integrity sha512-tKs41J7NJVuaya8DxIOCnl8QuPHx5/ZVbFo1oKgVl1qHFBBrDctzQGtuLjPpRdNTWmKPH6oEvgN/MUID+l485Q== -core-util-is@1.0.2, core-util-is@~1.0.0: +core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= @@ -1084,12 +1116,12 @@ css-what@^5.0.0, css-what@^5.0.1: resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.0.1.tgz#3efa820131f4669a8ac2408f9c32e7c7de9f4cad" integrity sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg== -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= +debug@4, debug@^4.1.0, debug@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== dependencies: - assert-plus "^1.0.0" + ms "2.1.2" debug@^2.6.8: version "2.6.9" @@ -1098,13 +1130,6 @@ debug@^2.6.8: dependencies: ms "2.0.0" -debug@^4.1.0, debug@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" - integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== - dependencies: - ms "2.1.2" - debug@^4.1.1, debug@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" @@ -1136,6 +1161,11 @@ defer-to-connect@^2.0.0: resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.0.tgz#83d6b199db041593ac84d781b5222308ccf4c2c1" integrity sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg== +define-lazy-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" + integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== + define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -1215,13 +1245,12 @@ duplexer3@^0.1.4: resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" + safe-buffer "^5.0.1" electron-osx-sign@^0.4.16: version "0.4.16" @@ -1327,11 +1356,6 @@ extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.2, extend@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - extract-zip@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" @@ -1343,22 +1367,7 @@ extract-zip@^2.0.1: optionalDependencies: "@types/yauzl" "^2.9.1" -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= - -extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= - -fast-deep-equal@^3.1.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -1370,10 +1379,10 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= +follow-redirects@^1.14.0: + version "1.14.5" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.5.tgz#f09a5848981d3c772b5392309778523f8d85c381" + integrity sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA== form-data@^3.0.0: version "3.0.0" @@ -1384,13 +1393,13 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== dependencies: asynckit "^0.4.0" - combined-stream "^1.0.6" + combined-stream "^1.0.8" mime-types "^2.1.12" fs-extra@^8.1.0: @@ -1445,13 +1454,6 @@ get-stream@^5.1.0: dependencies: pump "^3.0.0" -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= - dependencies: - assert-plus "^1.0.0" - glob@^7.0.6: version "7.1.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" @@ -1561,19 +1563,6 @@ gulp-merge-json@^2.1.1: through "^2.3.8" vinyl "^2.1.0" -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= - -har-validator@~5.1.3: - version "5.1.5" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" - integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== - dependencies: - ajv "^6.12.3" - har-schema "^2.0.0" - has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -1591,15 +1580,6 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -hash-base@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" - integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== - dependencies: - inherits "^2.0.4" - readable-stream "^3.6.0" - safe-buffer "^5.2.0" - hosted-git-info@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.0.2.tgz#5e425507eede4fea846b7262f0838456c4209961" @@ -1622,14 +1602,14 @@ http-cache-semantics@^4.0.0: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= +http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" + "@tootallnate/once" "1" + agent-base "6" + debug "4" http2-wrapper@^1.0.0-beta.5.2: version "1.0.0-beta.5.2" @@ -1639,10 +1619,18 @@ http2-wrapper@^1.0.0-beta.5.2: quick-lru "^5.1.1" resolve-alpn "^1.0.0" -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== +https-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + dependencies: + agent-base "6" + debug "4" + +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" @@ -1652,21 +1640,21 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@^2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -inherits@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - ini@^1.3.4: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +is-docker@^2.0.0, is-docker@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + is-extendable@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" @@ -1693,10 +1681,12 @@ is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" isarray@~1.0.0: version "1.0.0" @@ -1720,21 +1710,11 @@ isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= - jsbi@^3.1.3: version "3.1.4" resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-3.1.4.tgz#9654dd02207a66a4911b4e4bb74265bc2cbc9dd0" integrity sha512-52QRRFSsi9impURE8ZUbzAMCLjPm4THO7H2fcuIvaaeFTbSysvkodbQQXIVsNgq/ypDbq6dJiuGKL0vZ/i9hUg== -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= - json-buffer@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" @@ -1745,24 +1725,7 @@ json-buffer@3.0.1: resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== -json-edm-parser@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/json-edm-parser/-/json-edm-parser-0.1.2.tgz#1e60b0fef1bc0af67bc0d146dfdde5486cd615b4" - integrity sha1-HmCw/vG8CvZ7wNFG393lSGzWFbQ= - dependencies: - jsonparse "~1.2.0" - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= - -json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: +json-stringify-safe@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= @@ -1795,20 +1758,55 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jsonparse@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.2.0.tgz#5c0c5685107160e72fe7489bddea0b44c2bc67bd" - integrity sha1-XAxWhRBxYOcv50ib3eoLRMK8Z70= - -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= +jsonwebtoken@^8.5.1: + version "8.5.1" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" + integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.2.3" - verror "1.10.0" + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^5.6.0" + +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jwa@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc" + integrity sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + +jws@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4" + integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg== + dependencies: + jwa "^2.0.0" + safe-buffer "^5.0.1" keyv@^3.0.0: version "3.1.0" @@ -1836,11 +1834,46 @@ linkify-it@^2.0.0: dependencies: uc.micro "^1.0.1" +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= + lodash.mergewith@^4.6.1: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55" integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ== +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= + lodash.unescape@4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c" @@ -1886,14 +1919,6 @@ matcher@^3.0.0: dependencies: escape-string-regexp "^4.0.0" -md5.js@1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" - integrity sha1-6b296UogpawYsENA/Fdk1bCdkB0= - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - mdurl@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" @@ -1904,7 +1929,7 @@ mime-db@1.45.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea" integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w== -mime-types@^2.1.12, mime-types@~2.1.19: +mime-types@^2.1.12: version "2.1.28" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.28.tgz#1160c4757eab2c5363888e005273ecf79d2a0ecd" integrity sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ== @@ -1963,15 +1988,20 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + mute-stream@~0.0.4: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= -node-abort-controller@^1.0.4: - version "1.1.0" - resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-1.1.0.tgz#8a734a631b022af29963be7245c1483cbb9e070d" - integrity sha512-dEYmUqjtbivotqjraOe8UvhT/poFfog1BQRNsZm/MSEDDESk2cQ1tvD8kGyuN07TM/zoW+n42odL8zTeJupYdQ== +node-abort-controller@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-1.2.1.tgz#1eddb57eb8fea734198b11b28857596dc6165708" + integrity sha512-79PYeJuj6S9+yOHirR0JBLFOgjB6sQCir10uN6xRx25iD+ZD4ULqgRn3MwWBRaQGB0vEgReJzWwJo42T1R6YbQ== node-fetch@^2.6.0: version "2.6.1" @@ -2003,11 +2033,6 @@ nth-check@^2.0.0: dependencies: boolbase "^1.0.0" -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== - object-inspect@^1.9.0: version "1.11.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" @@ -2025,6 +2050,15 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" +open@^8.0.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" + integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== + dependencies: + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" + os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" @@ -2094,11 +2128,6 @@ pend@~1.2.0: resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= - picomatch@^2.0.4: version "2.3.0" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" @@ -2152,11 +2181,6 @@ process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -process-nextick-args@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" - integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M= - process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" @@ -2172,7 +2196,7 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= -psl@^1.1.28, psl@^1.1.33: +psl@^1.1.33: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== @@ -2185,7 +2209,7 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -punycode@^2.1.0, punycode@^2.1.1: +punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== @@ -2197,11 +2221,6 @@ qs@^6.9.1: dependencies: side-channel "^1.0.4" -qs@~6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== - quick-lru@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" @@ -2227,27 +2246,6 @@ readable-stream@^2.3.5: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readable-stream@~2.0.0: - version "2.0.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" - integrity sha1-j5A0HmilPMySh4jaz80Rs265t44= - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - string_decoder "~0.10.x" - util-deprecate "~1.0.1" - remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" @@ -2258,32 +2256,6 @@ replace-ext@^1.0.0: resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.1.tgz#2d6d996d04a15855d967443631dd5f77825b016a" integrity sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw== -request@^2.86.0: - version "2.88.2" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" - integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.3" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.5.0" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - resolve-alpn@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.0.0.tgz#745ad60b3d6aff4b4a48e01b8c0bdc70959e0e8c" @@ -2322,7 +2294,7 @@ roarr@^2.15.3: semver-compare "^1.0.0" sprintf-js "^1.1.2" -safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0: +safe-buffer@^5.0.1: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -2332,16 +2304,6 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sax@0.5.x: - version "0.5.8" - resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1" - integrity sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE= - sax@>=0.6.0: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -2362,6 +2324,11 @@ semver@^5.1.0, semver@^5.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== +semver@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + semver@^6.2.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" @@ -2422,33 +2389,18 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -sshpk@^1.7.0: - version "1.16.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" - integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" +stoppable@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/stoppable/-/stoppable-1.1.0.tgz#32da568e83ea488b08e4d7ea2c3bcc9d75015d5b" + integrity sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw== -string_decoder@^1.1.1, string_decoder@~1.1.1: +string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== dependencies: safe-buffer "~5.1.0" -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= - sumchecker@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" @@ -2496,14 +2448,6 @@ tough-cookie@^4.0.0: punycode "^2.1.1" universalify "^0.1.2" -tough-cookie@~2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" - integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== - dependencies: - psl "^1.1.28" - punycode "^2.1.1" - tslib@^1.8.1: version "1.9.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" @@ -2526,23 +2470,11 @@ tsutils@^3.17.1: dependencies: tslib "^1.8.1" -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= - dependencies: - safe-buffer "^5.0.1" - tunnel@0.0.6, tunnel@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= - type-fest@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" @@ -2557,10 +2489,10 @@ typed-rest-client@^1.8.4: tunnel "0.0.6" underscore "^1.12.1" -typescript@^4.6.0-dev.20211108: - version "4.6.0-dev.20211108" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.0-dev.20211108.tgz#d9e65b39f0876ba9d5e82b7955d1183c38d1e40b" - integrity sha512-5a0mWJq05zNPSb0vF4s6OJbCxT0Cz6XIjgkaiYy5pkSXlu2E8mYRnYlo9IKKn34eUFO5FiO0uwm0Z3dsbcEgDw== +typescript@^4.6.0-dev.20211115: + version "4.6.0-dev.20211115" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.0-dev.20211115.tgz#215e5d032e77cb83f382dc88e901a0757c02cc53" + integrity sha512-rYdYp/j8OhCRFs97l7GNOX9xGHndwwgY8AcL7LDzmFXgBOXC2VLoQP48nCg8FgVzjK6s0M5V4nijTYHRlwiqGQ== uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.5" @@ -2572,11 +2504,6 @@ underscore@^1.12.1: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.1.tgz#0c1c6bd2df54b6b69f2314066d65b6cde6fcf9d1" integrity sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g== -underscore@~1.8.3: - version "1.8.3" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022" - integrity sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI= - universal-user-agent@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" @@ -2592,13 +2519,6 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - url-join@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/url-join/-/url-join-1.1.0.tgz#741c6c2f4596c4830d6718460920d0c92202dc78" @@ -2611,35 +2531,16 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" -util-deprecate@^1.0.1, util-deprecate@~1.0.1: +util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -uuid@^3.0.0, uuid@^3.3.2: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - uuid@^8.3.0: version "8.3.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31" integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg== -validator@~9.4.1: - version "9.4.1" - resolved "https://registry.yarnpkg.com/validator/-/validator-9.4.1.tgz#abf466d398b561cd243050112c6ff1de6cc12663" - integrity sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA== - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - vinyl@^2.1.0: version "2.2.1" resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.1.tgz#23cfb8bbab5ece3803aa2c0a1eb28af7cbba1974" @@ -2703,13 +2604,6 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -xml2js@0.2.8: - version "0.2.8" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.2.8.tgz#9b81690931631ff09d1957549faf54f4f980b3c2" - integrity sha1-m4FpCTFjH/CdGVdUn69U9PmAs8I= - dependencies: - sax "0.5.x" - xml2js@^0.4.19, xml2js@^0.4.23: version "0.4.23" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" diff --git a/extensions/configuration-editing/schemas/attachContainer.schema.json b/extensions/configuration-editing/schemas/attachContainer.schema.json index 48d51d78f05..6d9d794265b 100644 --- a/extensions/configuration-editing/schemas/attachContainer.schema.json +++ b/extensions/configuration-editing/schemas/attachContainer.schema.json @@ -24,7 +24,7 @@ }, { "type": "string", - "pattern": "^([a-z0-9\\-]+):(\\d{1,5})$" + "pattern": "^([a-z0-9-]+):(\\d{1,5})$" } ] } @@ -32,7 +32,7 @@ "portsAttributes": { "type": "object", "patternProperties": { - "(^\\d+(\\-\\d+)?$)|(.+)": { + "(^\\d+(-\\d+)?$)|(.+)": { "type": "object", "description": "A port, range of ports (ex. \"40000-55000\"), or regular expression (ex. \".+\\\\/server.js\"). For a port number or range, the attributes will apply to that port number or range of port numbers. Attributes which use a regular expression will apply to ports whose associated process command line matches the expression.", "properties": { @@ -179,7 +179,7 @@ "description": "An array of extensions that should be installed into the container.", "items": { "type": "string", - "pattern": "^([a-z0-9A-Z][a-z0-9\\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\\-A-Z]*)(@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)?$", + "pattern": "^([a-z0-9A-Z][a-z0-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-Z-]*)(@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)?$", "errorMessage": "Expected format: '${publisher}.${name}' or '${publisher}.${name}@${version}'. Example: 'ms-dotnettools.csharp'." } }, diff --git a/extensions/configuration-editing/schemas/devContainer.schema.generated.json b/extensions/configuration-editing/schemas/devContainer.schema.generated.json index 19e769e95b0..b8a6d3cf5a3 100644 --- a/extensions/configuration-editing/schemas/devContainer.schema.generated.json +++ b/extensions/configuration-editing/schemas/devContainer.schema.generated.json @@ -116,7 +116,7 @@ "description": "An array of extensions that should be installed into the container.", "items": { "type": "string", - "pattern": "^([a-z0-9A-Z][a-z0-9\\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\\-A-Z]*)(@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)?$", + "pattern": "^([a-z0-9A-Z][a-z0-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-Z-]*)(@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)?$", "errorMessage": "Expected format: '${publisher}.${name}' or '${publisher}.${name}@${version}'. Example: 'ms-dotnettools.csharp'." } }, @@ -141,7 +141,7 @@ }, { "type": "string", - "pattern": "^([a-z0-9\\-]+):(\\d{1,5})$" + "pattern": "^([a-z0-9-]+):(\\d{1,5})$" } ] } @@ -149,7 +149,7 @@ "portsAttributes": { "type": "object", "patternProperties": { - "(^\\d+(\\-\\d+)?$)|(.+)": { + "(^\\d+(-\\d+)?$)|(.+)": { "type": "object", "description": "A port, range of ports (ex. \"40000-55000\"), or regular expression (ex. \".+\\\\/server.js\"). For a port number or range, the attributes will apply to that port number or range of port numbers. Attributes which use a regular expression will apply to ports whose associated process command line matches the expression.", "properties": { @@ -519,7 +519,7 @@ "description": "An array of extensions that should be installed into the container.", "items": { "type": "string", - "pattern": "^([a-z0-9A-Z][a-z0-9\\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\\-A-Z]*)(@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)?$", + "pattern": "^([a-z0-9A-Z][a-z0-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-Z-]*)(@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)?$", "errorMessage": "Expected format: '${publisher}.${name}' or '${publisher}.${name}@${version}'. Example: 'ms-dotnettools.csharp'." } }, @@ -544,7 +544,7 @@ }, { "type": "string", - "pattern": "^([a-z0-9\\-]+):(\\d{1,5})$" + "pattern": "^([a-z0-9-]+):(\\d{1,5})$" } ] } @@ -552,7 +552,7 @@ "portsAttributes": { "type": "object", "patternProperties": { - "(^\\d+(\\-\\d+)?$)|(.+)": { + "(^\\d+(-\\d+)?$)|(.+)": { "type": "object", "description": "A port, range of ports (ex. \"40000-55000\"), or regular expression (ex. \".+\\\\/server.js\"). For a port number or range, the attributes will apply to that port number or range of port numbers. Attributes which use a regular expression will apply to ports whose associated process command line matches the expression.", "properties": { @@ -888,7 +888,7 @@ "description": "An array of extensions that should be installed into the container.", "items": { "type": "string", - "pattern": "^([a-z0-9A-Z][a-z0-9\\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\\-A-Z]*)(@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)?$", + "pattern": "^([a-z0-9A-Z][a-z0-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-Z-]*)(@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)?$", "errorMessage": "Expected format: '${publisher}.${name}' or '${publisher}.${name}@${version}'. Example: 'ms-dotnettools.csharp'." } }, @@ -913,7 +913,7 @@ }, { "type": "string", - "pattern": "^([a-z0-9\\-]+):(\\d{1,5})$" + "pattern": "^([a-z0-9-]+):(\\d{1,5})$" } ] } @@ -921,7 +921,7 @@ "portsAttributes": { "type": "object", "patternProperties": { - "(^\\d+(\\-\\d+)?$)|(.+)": { + "(^\\d+(-\\d+)?$)|(.+)": { "type": "object", "description": "A port, range of ports (ex. \"40000-55000\"), or regular expression (ex. \".+\\\\/server.js\"). For a port number or range, the attributes will apply to that port number or range of port numbers. Attributes which use a regular expression will apply to ports whose associated process command line matches the expression.", "properties": { @@ -1231,7 +1231,7 @@ "description": "An array of extensions that should be installed into the container.", "items": { "type": "string", - "pattern": "^([a-z0-9A-Z][a-z0-9\\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\\-A-Z]*)(@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)?$", + "pattern": "^([a-z0-9A-Z][a-z0-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-Z-]*)(@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)?$", "errorMessage": "Expected format: '${publisher}.${name}' or '${publisher}.${name}@${version}'. Example: 'ms-dotnettools.csharp'." } }, @@ -1256,7 +1256,7 @@ }, { "type": "string", - "pattern": "^([a-z0-9\\-]+):(\\d{1,5})$" + "pattern": "^([a-z0-9-]+):(\\d{1,5})$" } ] } @@ -1264,7 +1264,7 @@ "portsAttributes": { "type": "object", "patternProperties": { - "(^\\d+(\\-\\d+)?$)|(.+)": { + "(^\\d+(-\\d+)?$)|(.+)": { "type": "object", "description": "A port, range of ports (ex. \"40000-55000\"), or regular expression (ex. \".+\\\\/server.js\"). For a port number or range, the attributes will apply to that port number or range of port numbers. Attributes which use a regular expression will apply to ports whose associated process command line matches the expression.", "properties": { @@ -1539,7 +1539,7 @@ "description": "An array of extensions that should be installed into the container.", "items": { "type": "string", - "pattern": "^([a-z0-9A-Z][a-z0-9\\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\\-A-Z]*)(@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)?$", + "pattern": "^([a-z0-9A-Z][a-z0-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-Z-]*)(@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)?$", "errorMessage": "Expected format: '${publisher}.${name}' or '${publisher}.${name}@${version}'. Example: 'ms-dotnettools.csharp'." } }, @@ -1564,7 +1564,7 @@ }, { "type": "string", - "pattern": "^([a-z0-9\\-]+):(\\d{1,5})$" + "pattern": "^([a-z0-9-]+):(\\d{1,5})$" } ] } @@ -1572,7 +1572,7 @@ "portsAttributes": { "type": "object", "patternProperties": { - "(^\\d+(\\-\\d+)?$)|(.+)": { + "(^\\d+(-\\d+)?$)|(.+)": { "type": "object", "description": "A port, range of ports (ex. \"40000-55000\"), or regular expression (ex. \".+\\\\/server.js\"). For a port number or range, the attributes will apply to that port number or range of port numbers. Attributes which use a regular expression will apply to ports whose associated process command line matches the expression.", "properties": { diff --git a/extensions/configuration-editing/schemas/devContainer.schema.src.json b/extensions/configuration-editing/schemas/devContainer.schema.src.json index e42d3655d2a..7a11e21a01b 100644 --- a/extensions/configuration-editing/schemas/devContainer.schema.src.json +++ b/extensions/configuration-editing/schemas/devContainer.schema.src.json @@ -16,7 +16,7 @@ "description": "An array of extensions that should be installed into the container.", "items": { "type": "string", - "pattern": "^([a-z0-9A-Z][a-z0-9\\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\\-A-Z]*)(@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)?$", + "pattern": "^([a-z0-9A-Z][a-z0-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-Z-]*)(@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)?$", "errorMessage": "Expected format: '${publisher}.${name}' or '${publisher}.${name}@${version}'. Example: 'ms-dotnettools.csharp'." } }, @@ -41,7 +41,7 @@ }, { "type": "string", - "pattern": "^([a-z0-9\\-]+):(\\d{1,5})$" + "pattern": "^([a-z0-9-]+):(\\d{1,5})$" } ] } @@ -49,7 +49,7 @@ "portsAttributes": { "type": "object", "patternProperties": { - "(^\\d+(\\-\\d+)?$)|(.+)": { + "(^\\d+(-\\d+)?$)|(.+)": { "type": "object", "description": "A port, range of ports (ex. \"40000-55000\"), or regular expression (ex. \".+\\\\/server.js\"). For a port number or range, the attributes will apply to that port number or range of port numbers. Attributes which use a regular expression will apply to ports whose associated process command line matches the expression.", "properties": { diff --git a/extensions/configuration-editing/src/settingsDocumentHelper.ts b/extensions/configuration-editing/src/settingsDocumentHelper.ts index e6afeb22d1d..11282899b81 100644 --- a/extensions/configuration-editing/src/settingsDocumentHelper.ts +++ b/extensions/configuration-editing/src/settingsDocumentHelper.ts @@ -9,6 +9,7 @@ import * as nls from 'vscode-nls'; import { provideInstalledExtensionProposals } from './extensionsProposals'; const localize = nls.loadMessageBundle(); +const OVERRIDE_IDENTIFIER_REGEX = /\[([^\[\]]*)\]/g; export class SettingsDocument { @@ -186,61 +187,60 @@ export class SettingsDocument { .then(languages => languages.map(l => this.newSimpleCompletionItem(formatFunc(l), range))); } - private provideLanguageCompletionItemsForLanguageOverrides(_location: Location, range: vscode.Range, formatFunc: (string: string) => string = (l) => JSON.stringify(l)): Thenable { - return vscode.languages.getLanguages().then(languages => { - const completionItems = []; - const configuration = vscode.workspace.getConfiguration(); - for (const language of languages) { - const inspect = configuration.inspect(`[${language}]`); - if (!inspect || !inspect.defaultValue) { - const item = new vscode.CompletionItem(formatFunc(language)); - item.kind = vscode.CompletionItemKind.Property; - item.range = range; - completionItems.push(item); - } - } - return completionItems; - }); + private async provideLanguageCompletionItemsForLanguageOverrides(_location: Location, range: vscode.Range): Promise { + const languages = await vscode.languages.getLanguages(); + const completionItems = []; + for (const language of languages) { + const item = new vscode.CompletionItem(JSON.stringify(language)); + item.kind = vscode.CompletionItemKind.Property; + item.range = range; + completionItems.push(item); + } + return completionItems; } - private provideLanguageOverridesCompletionItems(location: Location, position: vscode.Position): vscode.ProviderResult { - - if (location.path.length === 0) { - - let range = this.document.getWordRangeAtPosition(position, /^\s*\[.*]?/) || new vscode.Range(position, position); - let text = this.document.getText(range); - if (text && text.trim().startsWith('[')) { - range = new vscode.Range(new vscode.Position(range.start.line, range.start.character + text.indexOf('[')), range.end); - return this.provideLanguageCompletionItemsForLanguageOverrides(location, range, language => `"[${language}]"`); - } - - range = this.document.getWordRangeAtPosition(position) || new vscode.Range(position, position); - text = this.document.getText(range); - let snippet = '"[${1:language}]": {\n\t"$0"\n}'; - - // Suggestion model word matching includes quotes, - // hence exclude the starting quote from the snippet and the range - // ending quote gets replaced - if (text && text.startsWith('"')) { - range = new vscode.Range(new vscode.Position(range.start.line, range.start.character + 1), range.end); - snippet = snippet.substring(1); - } - - return Promise.resolve([this.newSnippetCompletionItem({ - label: localize('languageSpecificEditorSettings', "Language specific editor settings"), - documentation: localize('languageSpecificEditorSettingsDescription', "Override editor settings for language"), - snippet, - range - })]); - } - + private async provideLanguageOverridesCompletionItems(location: Location, position: vscode.Position): Promise { if (location.path.length === 1 && location.previousNode && typeof location.previousNode.value === 'string' && location.previousNode.value.startsWith('[')) { - // Suggestion model word matching includes closed sqaure bracket and ending quote - // Hence include them in the proposal to replace - const range = this.document.getWordRangeAtPosition(position) || new vscode.Range(position, position); - return this.provideLanguageCompletionItemsForLanguageOverrides(location, range, language => `"[${language}]"`); + const startPosition = this.document.positionAt(location.previousNode.offset + 1); + const endPosition = startPosition.translate(undefined, location.previousNode.value.length); + const donotSuggestLanguages: string[] = []; + const languageOverridesRanges: vscode.Range[] = []; + let matches = OVERRIDE_IDENTIFIER_REGEX.exec(location.previousNode.value); + let lastLanguageOverrideRange: vscode.Range | undefined; + while (matches?.length) { + lastLanguageOverrideRange = new vscode.Range(this.document.positionAt(location.previousNode.offset + 1 + matches.index), this.document.positionAt(location.previousNode.offset + 1 + matches.index + matches[0].length)); + languageOverridesRanges.push(lastLanguageOverrideRange); + /* Suggest the configured language if the position is in the match range */ + if (!lastLanguageOverrideRange.contains(position)) { + donotSuggestLanguages.push(matches[1].trim()); + } + matches = OVERRIDE_IDENTIFIER_REGEX.exec(location.previousNode.value); + } + const lastLanguageOverrideEndPosition = lastLanguageOverrideRange ? lastLanguageOverrideRange.end : startPosition; + if (lastLanguageOverrideEndPosition.isBefore(endPosition)) { + languageOverridesRanges.push(new vscode.Range(lastLanguageOverrideEndPosition, endPosition)); + } + const languageOverrideRange = languageOverridesRanges.find(range => range.contains(position)); + + /** + * Skip if suggestsions are for first language override range + * Since VSCode registers language overrides to the schema, JSON language server does suggestions for first language override. + */ + if (languageOverrideRange && !languageOverrideRange.isEqual(languageOverridesRanges[0])) { + const languages = await vscode.languages.getLanguages(); + const completionItems = []; + for (const language of languages) { + if (!donotSuggestLanguages.includes(language)) { + const item = new vscode.CompletionItem(`[${language}]`); + item.kind = vscode.CompletionItemKind.Property; + item.range = languageOverrideRange; + completionItems.push(item); + } + } + return completionItems; + } } - return Promise.resolve([]); + return []; } private providePortsAttributesCompletionItem(range: vscode.Range): vscode.CompletionItem[] { diff --git a/extensions/css-language-features/client/tsconfig.json b/extensions/css-language-features/client/tsconfig.json index 1e11c9aaccc..573b24b4aa6 100644 --- a/extensions/css-language-features/client/tsconfig.json +++ b/extensions/css-language-features/client/tsconfig.json @@ -5,7 +5,6 @@ }, "include": [ "src/**/*", - "../../../src/vscode-dts/vscode.d.ts", - "../../../src/vscode-dts/vscode.proposed.d.ts", + "../../../src/vscode-dts/vscode.d.ts" ] } diff --git a/extensions/css-language-features/package.json b/extensions/css-language-features/package.json index 7c3b8e75472..88994a67f44 100644 --- a/extensions/css-language-features/package.json +++ b/extensions/css-language-features/package.json @@ -17,7 +17,6 @@ ], "main": "./client/out/node/cssClientMain", "browser": "./client/dist/browser/cssClientMain", - "enableProposedApi": true, "capabilities": { "virtualWorkspaces": true, "untrustedWorkspaces": { diff --git a/extensions/debug-server-ready/package.json b/extensions/debug-server-ready/package.json index e5c69df006b..64aa227aa53 100644 --- a/extensions/debug-server-ready/package.json +++ b/extensions/debug-server-ready/package.json @@ -18,7 +18,9 @@ "supported": true } }, - "enableProposedApi": true, + "enabledApiProposals": [ + "terminalDataWriteEvent" + ], "main": "./out/extension", "scripts": { "compile": "gulp compile-extension:debug-server-ready", diff --git a/extensions/debug-server-ready/tsconfig.json b/extensions/debug-server-ready/tsconfig.json index 88c43e9eba8..9bf747283ca 100644 --- a/extensions/debug-server-ready/tsconfig.json +++ b/extensions/debug-server-ready/tsconfig.json @@ -10,6 +10,6 @@ "include": [ "src/**/*", "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.d.ts", + "../../src/vscode-dts/vscode.proposed.terminalDataWriteEvent.d.ts", ] } diff --git a/extensions/diff/.vscodeignore b/extensions/diff/.vscodeignore new file mode 100644 index 00000000000..d9011becfb6 --- /dev/null +++ b/extensions/diff/.vscodeignore @@ -0,0 +1,2 @@ +build/** +cgmanifest.json diff --git a/extensions/diff/cgmanifest.json b/extensions/diff/cgmanifest.json new file mode 100644 index 00000000000..04d6573c95e --- /dev/null +++ b/extensions/diff/cgmanifest.json @@ -0,0 +1,32 @@ +{ + "registrations": [ + { + "component": { + "type": "git", + "git": { + "name": "textmate/diff.tmbundle", + "repositoryUrl": "https://github.com/textmate/diff.tmbundle", + "commitHash": "0593bb775eab1824af97ef2172fd38822abd97d7" + } + }, + "licenseDetail": [ + "Copyright (c) textmate-diff.tmbundle project authors", + "", + "If not otherwise specified (see below), files in this repository fall under the following license:", + "", + "Permission to copy, use, modify, sell and distribute this", + "software is granted. This software is provided \"as is\" without", + "express or implied warranty, and with no claim as to its", + "suitability for any purpose.", + "", + "An exception is made for files in readable text which contain their own license information,", + "or files where an accompanying file exists (in the same directory) with a \"-license\" suffix added", + "to the base-name name of the original file, and an extension of txt, html, or similar. For example", + "\"tidy\" is accompanied by \"tidy-license.txt\"." + ], + "license": "TextMate Bundle License", + "version": "0.0.0" + } + ], + "version": 1 +} diff --git a/extensions/git/languages/diff.language-configuration.json b/extensions/diff/language-configuration.json similarity index 98% rename from extensions/git/languages/diff.language-configuration.json rename to extensions/diff/language-configuration.json index b61fbe63d34..395aff60535 100644 --- a/extensions/git/languages/diff.language-configuration.json +++ b/extensions/diff/language-configuration.json @@ -8,4 +8,4 @@ ["[", "]"], ["(", ")"] ] -} \ No newline at end of file +} diff --git a/extensions/diff/package.json b/extensions/diff/package.json new file mode 100644 index 00000000000..7d23e24ac6b --- /dev/null +++ b/extensions/diff/package.json @@ -0,0 +1,38 @@ +{ + "name": "diff", + "displayName": "%displayName%", + "description": "%description%", + "version": "1.0.0", + "publisher": "vscode", + "license": "MIT", + "engines": { + "vscode": "0.10.x" + }, + "scripts": { + "update-grammar": "node ../node_modules/vscode-grammar-updater/bin textmate/diff.tmbundle Syntaxes/Diff.plist ./syntaxes/diff.tmLanguage.json" + }, + "contributes": { + "languages": [ + { + "id": "diff", + "aliases": [ + "Diff", + "diff" + ], + "extensions": [ + ".diff", + ".patch", + ".rej" + ], + "configuration": "./language-configuration.json" + } + ], + "grammars": [ + { + "language": "diff", + "scopeName": "source.diff", + "path": "./syntaxes/diff.tmLanguage.json" + } + ] + } +} diff --git a/extensions/diff/package.nls.json b/extensions/diff/package.nls.json new file mode 100644 index 00000000000..c869050177e --- /dev/null +++ b/extensions/diff/package.nls.json @@ -0,0 +1,4 @@ +{ + "displayName": "Diff Language Basics", + "description": "Provides syntax highlighting & bracket matching in Diff files." +} diff --git a/extensions/git/syntaxes/diff.tmLanguage.json b/extensions/diff/syntaxes/diff.tmLanguage.json similarity index 100% rename from extensions/git/syntaxes/diff.tmLanguage.json rename to extensions/diff/syntaxes/diff.tmLanguage.json diff --git a/extensions/emmet/package.json b/extensions/emmet/package.json index 6c1f0eb8eda..3dfc882b432 100644 --- a/extensions/emmet/package.json +++ b/extensions/emmet/package.json @@ -41,24 +41,7 @@ "onCommand:editor.emmet.action.decrementNumberByTen", "onCommand:editor.emmet.action.reflectCSSValue", "onCommand:workbench.action.showEmmetCommands", - "onLanguage:html", - "onLanguage:haml", - "onLanguage:blade", - "onLanguage:css", - "onLanguage:sass", - "onLanguage:scss", - "onLanguage:less", - "onLanguage:stylus", - "onLanguage:slim", - "onLanguage:stylus", - "onLanguage:jade", - "onLanguage:pug", - "onLanguage:javascriptreact", - "onLanguage:typescriptreact", - "onLanguage:php", - "onLanguage:vue", - "onLanguage:xsl", - "onLanguage:xml" + "onStartupFinished" ], "main": "./out/node/emmetNodeMain", "browser": "./dist/browser/emmetBrowserMain", diff --git a/extensions/git-base/.vscodeignore b/extensions/git-base/.vscodeignore new file mode 100644 index 00000000000..d9011becfb6 --- /dev/null +++ b/extensions/git-base/.vscodeignore @@ -0,0 +1,2 @@ +build/** +cgmanifest.json diff --git a/extensions/git/build/update-grammars.js b/extensions/git-base/build/update-grammars.js similarity index 86% rename from extensions/git/build/update-grammars.js rename to extensions/git-base/build/update-grammars.js index a5dae9f6850..ad326bff374 100644 --- a/extensions/git/build/update-grammars.js +++ b/extensions/git-base/build/update-grammars.js @@ -8,9 +8,3 @@ var updateGrammar = require('vscode-grammar-updater'); updateGrammar.update('textmate/git.tmbundle', 'Syntaxes/Git%20Commit%20Message.tmLanguage', './syntaxes/git-commit.tmLanguage.json'); updateGrammar.update('textmate/git.tmbundle', 'Syntaxes/Git%20Rebase%20Message.tmLanguage', './syntaxes/git-rebase.tmLanguage.json'); -updateGrammar.update('textmate/diff.tmbundle', 'Syntaxes/Diff.plist', './syntaxes/diff.tmLanguage.json'); - - - - - diff --git a/extensions/git/cgmanifest.json b/extensions/git-base/cgmanifest.json similarity index 57% rename from extensions/git/cgmanifest.json rename to extensions/git-base/cgmanifest.json index e8081d6472e..256966aba20 100644 --- a/extensions/git/cgmanifest.json +++ b/extensions/git-base/cgmanifest.json @@ -33,34 +33,7 @@ ], "license": "MIT", "version": "0.0.0" - }, - { - "component": { - "type": "git", - "git": { - "name": "textmate/diff.tmbundle", - "repositoryUrl": "https://github.com/textmate/diff.tmbundle", - "commitHash": "0593bb775eab1824af97ef2172fd38822abd97d7" - } - }, - "licenseDetail": [ - "Copyright (c) textmate-diff.tmbundle project authors", - "", - "If not otherwise specified (see below), files in this repository fall under the following license:", - "", - "Permission to copy, use, modify, sell and distribute this", - "software is granted. This software is provided \"as is\" without", - "express or implied warranty, and with no claim as to its", - "suitability for any purpose.", - "", - "An exception is made for files in readable text which contain their own license information,", - "or files where an accompanying file exists (in the same directory) with a \"-license\" suffix added", - "to the base-name name of the original file, and an extension of txt, html, or similar. For example", - "\"tidy\" is accompanied by \"tidy-license.txt\"." - ], - "license": "TextMate Bundle License", - "version": "0.0.0" } ], "version": 1 -} \ No newline at end of file +} diff --git a/extensions/git/languages/git-commit.language-configuration.json b/extensions/git-base/languages/git-commit.language-configuration.json similarity index 100% rename from extensions/git/languages/git-commit.language-configuration.json rename to extensions/git-base/languages/git-commit.language-configuration.json diff --git a/extensions/git/languages/git-rebase.language-configuration.json b/extensions/git-base/languages/git-rebase.language-configuration.json similarity index 100% rename from extensions/git/languages/git-rebase.language-configuration.json rename to extensions/git-base/languages/git-rebase.language-configuration.json diff --git a/extensions/git/languages/ignore.language-configuration.json b/extensions/git-base/languages/ignore.language-configuration.json similarity index 100% rename from extensions/git/languages/ignore.language-configuration.json rename to extensions/git-base/languages/ignore.language-configuration.json diff --git a/extensions/git-base/package.json b/extensions/git-base/package.json new file mode 100644 index 00000000000..db3cbb7a812 --- /dev/null +++ b/extensions/git-base/package.json @@ -0,0 +1,70 @@ +{ + "name": "git-base", + "displayName": "%displayName%", + "description": "%description%", + "version": "1.0.0", + "publisher": "vscode", + "license": "MIT", + "engines": { + "vscode": "0.10.x" + }, + "scripts": { + "update-grammar": "node ./build/update-grammars.js" + }, + "contributes": { + "languages": [ + { + "id": "git-commit", + "aliases": [ + "Git Commit Message", + "git-commit" + ], + "filenames": [ + "COMMIT_EDITMSG", + "MERGE_MSG" + ], + "configuration": "./languages/git-commit.language-configuration.json" + }, + { + "id": "git-rebase", + "aliases": [ + "Git Rebase Message", + "git-rebase" + ], + "filenames": [ + "git-rebase-todo" + ], + "configuration": "./languages/git-rebase.language-configuration.json" + }, + { + "id": "ignore", + "aliases": [ + "Ignore", + "ignore" + ], + "extensions": [ + ".gitignore_global", + ".gitignore" + ], + "configuration": "./languages/ignore.language-configuration.json" + } + ], + "grammars": [ + { + "language": "git-commit", + "scopeName": "text.git-commit", + "path": "./syntaxes/git-commit.tmLanguage.json" + }, + { + "language": "git-rebase", + "scopeName": "text.git-rebase", + "path": "./syntaxes/git-rebase.tmLanguage.json" + }, + { + "language": "ignore", + "scopeName": "source.ignore", + "path": "./syntaxes/ignore.tmLanguage.json" + } + ] + } +} diff --git a/extensions/git-base/package.nls.json b/extensions/git-base/package.nls.json new file mode 100644 index 00000000000..4c1acedb648 --- /dev/null +++ b/extensions/git-base/package.nls.json @@ -0,0 +1,4 @@ +{ + "displayName": "Git Base", + "description": "Git static contributions and pickers." +} diff --git a/extensions/git/syntaxes/git-commit.tmLanguage.json b/extensions/git-base/syntaxes/git-commit.tmLanguage.json similarity index 100% rename from extensions/git/syntaxes/git-commit.tmLanguage.json rename to extensions/git-base/syntaxes/git-commit.tmLanguage.json diff --git a/extensions/git/syntaxes/git-rebase.tmLanguage.json b/extensions/git-base/syntaxes/git-rebase.tmLanguage.json similarity index 100% rename from extensions/git/syntaxes/git-rebase.tmLanguage.json rename to extensions/git-base/syntaxes/git-rebase.tmLanguage.json diff --git a/extensions/git/syntaxes/ignore.tmLanguage.json b/extensions/git-base/syntaxes/ignore.tmLanguage.json similarity index 100% rename from extensions/git/syntaxes/ignore.tmLanguage.json rename to extensions/git-base/syntaxes/ignore.tmLanguage.json diff --git a/extensions/git/.vscodeignore b/extensions/git/.vscodeignore index 7462f7448d3..94d2dc921e0 100644 --- a/extensions/git/.vscodeignore +++ b/extensions/git/.vscodeignore @@ -4,5 +4,4 @@ out/** tsconfig.json build/** extension.webpack.config.js -cgmanifest.json yarn.lock diff --git a/extensions/git/package.json b/extensions/git/package.json index b778540aa91..a26962bab09 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -9,7 +9,14 @@ "vscode": "^1.5.0" }, "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", - "enableProposedApi": true, + "enabledApiProposals": [ + "diffCommand", + "contribViewsWelcome", + "scmActionButton", + "scmSelectedProvider", + "scmValidation", + "timeline" + ], "categories": [ "Other" ], @@ -23,7 +30,6 @@ "compile": "gulp compile-extension:git", "watch": "gulp watch-extension:git", "update-emoji": "node ./build/update-emoji.js", - "update-grammar": "node ./build/update-grammars.js", "test": "node ../../node_modules/mocha/bin/mocha" }, "capabilities": { @@ -75,7 +81,8 @@ "command": "git.openChange", "title": "%command.openChange%", "category": "Git", - "icon": "$(compare-changes)" + "icon": "$(compare-changes)", + "enablement": "scmActiveResourceHasChanges" }, { "command": "git.openAllChanges", @@ -2290,78 +2297,6 @@ } } ], - "languages": [ - { - "id": "git-commit", - "aliases": [ - "Git Commit Message", - "git-commit" - ], - "filenames": [ - "COMMIT_EDITMSG", - "MERGE_MSG" - ], - "configuration": "./languages/git-commit.language-configuration.json" - }, - { - "id": "git-rebase", - "aliases": [ - "Git Rebase Message", - "git-rebase" - ], - "filenames": [ - "git-rebase-todo" - ], - "configuration": "./languages/git-rebase.language-configuration.json" - }, - { - "id": "diff", - "aliases": [ - "Diff", - "diff" - ], - "extensions": [ - ".diff", - ".patch", - ".rej" - ], - "configuration": "./languages/diff.language-configuration.json" - }, - { - "id": "ignore", - "aliases": [ - "Ignore", - "ignore" - ], - "extensions": [ - ".gitignore_global", - ".gitignore" - ], - "configuration": "./languages/ignore.language-configuration.json" - } - ], - "grammars": [ - { - "language": "git-commit", - "scopeName": "text.git-commit", - "path": "./syntaxes/git-commit.tmLanguage.json" - }, - { - "language": "git-rebase", - "scopeName": "text.git-rebase", - "path": "./syntaxes/git-rebase.tmLanguage.json" - }, - { - "language": "diff", - "scopeName": "source.diff", - "path": "./syntaxes/diff.tmLanguage.json" - }, - { - "language": "ignore", - "scopeName": "source.ignore", - "path": "./syntaxes/ignore.tmLanguage.json" - } - ], "configurationDefaults": { "[git-commit]": { "editor.rulers": [ @@ -2431,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", diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 54f5dc55b57..8f6d5c60d88 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1944,8 +1944,8 @@ export class Repository implements Disposable { } else { actionButton = { command: 'git.publish', - title: localize('scm button publish title', "$(cloud-upload) Publish Changes"), - tooltip: localize('scm button publish tooltip', "Publish Changes"), + title: localize('scm button publish title', "$(cloud-upload) Publish Branch"), + tooltip: localize('scm button publish tooltip', "Publish Branch"), arguments: [this._sourceControl], }; } diff --git a/extensions/git/src/statusbar.ts b/extensions/git/src/statusbar.ts index fd556024f83..1de71ad94a0 100644 --- a/extensions/git/src/statusbar.ts +++ b/extensions/git/src/statusbar.ts @@ -154,7 +154,7 @@ class SyncStatusBar { } else { icon = '$(cloud-upload)'; command = 'git.publish'; - tooltip = localize('publish changes', "Publish Changes"); + tooltip = localize('publish branch', "Publish Branch"); } } else { command = ''; diff --git a/extensions/git/src/timelineProvider.ts b/extensions/git/src/timelineProvider.ts index e43a2a0b84f..f6e543257e2 100644 --- a/extensions/git/src/timelineProvider.ts +++ b/extensions/git/src/timelineProvider.ts @@ -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) { diff --git a/extensions/git/test/mocha.opts b/extensions/git/test/mocha.opts deleted file mode 100644 index 93c2e8fffb6..00000000000 --- a/extensions/git/test/mocha.opts +++ /dev/null @@ -1 +0,0 @@ ---ui tdd out/test \ No newline at end of file diff --git a/extensions/git/tsconfig.json b/extensions/git/tsconfig.json index 7f8065b28e7..99129079e48 100644 --- a/extensions/git/tsconfig.json +++ b/extensions/git/tsconfig.json @@ -10,7 +10,11 @@ "include": [ "src/**/*", "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.d.ts", + "../../src/vscode-dts/vscode.proposed.diffCommand.d.ts", + "../../src/vscode-dts/vscode.proposed.scmActionButton.d.ts", + "../../src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts", + "../../src/vscode-dts/vscode.proposed.scmValidation.d.ts", + "../../src/vscode-dts/vscode.proposed.timeline.d.ts", "../types/lib.textEncoder.d.ts" ] } diff --git a/extensions/git/yarn.lock b/extensions/git/yarn.lock index 36a12fb119e..ff535a1ee3a 100644 --- a/extensions/git/yarn.lock +++ b/extensions/git/yarn.lock @@ -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" diff --git a/extensions/github-authentication/package.json b/extensions/github-authentication/package.json index 45c6a4b5175..2dbf469fd26 100644 --- a/extensions/github-authentication/package.json +++ b/extensions/github-authentication/package.json @@ -9,7 +9,6 @@ "vscode": "^1.41.0" }, "icon": "images/icon.png", - "enableProposedApi": true, "categories": [ "Other" ], diff --git a/extensions/github-authentication/tsconfig.json b/extensions/github-authentication/tsconfig.json index 5001991c9a6..d7aed1836ee 100644 --- a/extensions/github-authentication/tsconfig.json +++ b/extensions/github-authentication/tsconfig.json @@ -9,7 +9,6 @@ }, "include": [ "src/**/*", - "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.d.ts" + "../../src/vscode-dts/vscode.d.ts" ] } diff --git a/extensions/github/package.json b/extensions/github/package.json index f21080ab6f1..5e3e72a7b1c 100644 --- a/extensions/github/package.json +++ b/extensions/github/package.json @@ -9,7 +9,6 @@ "vscode": "^1.41.0" }, "icon": "images/icon.png", - "enableProposedApi": true, "categories": [ "Other" ], diff --git a/extensions/github/tsconfig.json b/extensions/github/tsconfig.json index 5001991c9a6..d7aed1836ee 100644 --- a/extensions/github/tsconfig.json +++ b/extensions/github/tsconfig.json @@ -9,7 +9,6 @@ }, "include": [ "src/**/*", - "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.d.ts" + "../../src/vscode-dts/vscode.d.ts" ] } diff --git a/extensions/html-language-features/client/tsconfig.json b/extensions/html-language-features/client/tsconfig.json index 959c5d37818..573b24b4aa6 100644 --- a/extensions/html-language-features/client/tsconfig.json +++ b/extensions/html-language-features/client/tsconfig.json @@ -5,7 +5,6 @@ }, "include": [ "src/**/*", - "../../../src/vscode-dts/vscode.d.ts", - "../../../src/vscode-dts/vscode.proposed.d.ts" + "../../../src/vscode-dts/vscode.d.ts" ] } diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json index ddaf33bcb65..07bbfe29e9a 100644 --- a/extensions/html-language-features/package.json +++ b/extensions/html-language-features/package.json @@ -1,5 +1,4 @@ { - "enableProposedApi": true, "name": "html-language-features", "displayName": "%displayName%", "description": "%description%", diff --git a/extensions/image-preview/package.json b/extensions/image-preview/package.json index c9aa3d23a85..8b730de9790 100644 --- a/extensions/image-preview/package.json +++ b/extensions/image-preview/package.json @@ -9,7 +9,6 @@ "version": "1.0.0", "publisher": "vscode", "icon": "icon.png", - "enableProposedApi": true, "license": "MIT", "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", "engines": { diff --git a/extensions/image-preview/tsconfig.json b/extensions/image-preview/tsconfig.json index c9980a21e38..c5194e2e33c 100644 --- a/extensions/image-preview/tsconfig.json +++ b/extensions/image-preview/tsconfig.json @@ -6,7 +6,6 @@ }, "include": [ "src/**/*", - "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.d.ts" + "../../src/vscode-dts/vscode.d.ts" ] } diff --git a/extensions/ipynb/package.json b/extensions/ipynb/package.json index cffba92357d..39c35edbb4f 100644 --- a/extensions/ipynb/package.json +++ b/extensions/ipynb/package.json @@ -8,7 +8,10 @@ "engines": { "vscode": "^1.57.0" }, - "enableProposedApi": true, + "enabledApiProposals": [ + "notebookEditor", + "notebookEditorEdit" + ], "activationEvents": [ "onNotebook:jupyter-notebook" ], @@ -29,23 +32,23 @@ { "id": "jupyter", "aliases": [ - "Jupyter" + "Jupyter (JSON)" ], "extensions": [ ".ipynb" ] } ], - "grammars": [ - { - "language": "jupyter", - "scopeName": "source.jupyter", - "path": "./syntaxes/jupyter.tmLanguage.json", - "embeddedLanguages": { - "source.json": "json" - } - } - ], + "grammars": [ + { + "language": "jupyter", + "scopeName": "source.jupyter", + "path": "./syntaxes/jupyter.tmLanguage.json", + "embeddedLanguages": { + "source.json": "json" + } + } + ], "notebooks": [ { "type": "jupyter-notebook", @@ -59,10 +62,10 @@ } ] }, - "scripts": { - "compile": "npx gulp compile-extension:ipynb", - "watch": "npx gulp watch-extension:ipynb" - }, + "scripts": { + "compile": "npx gulp compile-extension:ipynb", + "watch": "npx gulp watch-extension:ipynb" + }, "dependencies": { "@enonic/fnv-plus": "^1.3.0", "detect-indent": "^6.0.0", @@ -72,8 +75,8 @@ "@jupyterlab/coreutils": "^3.1.0", "@types/uuid": "^8.3.1" }, - "repository": { - "type": "git", - "url": "https://github.com/microsoft/vscode.git" - } + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode.git" + } } diff --git a/extensions/ipynb/tsconfig.json b/extensions/ipynb/tsconfig.json index 760d355845b..2032bf87b0d 100644 --- a/extensions/ipynb/tsconfig.json +++ b/extensions/ipynb/tsconfig.json @@ -9,6 +9,7 @@ "include": [ "src/**/*", "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.d.ts" + "../../src/vscode-dts/vscode.proposed.notebookEditor.d.ts", + "../../src/vscode-dts/vscode.proposed.notebookEditorEdit.d.ts", ] } diff --git a/extensions/json-language-features/client/src/browser/jsonClientMain.ts b/extensions/json-language-features/client/src/browser/jsonClientMain.ts index 6389dafb5ba..9eb390b545c 100644 --- a/extensions/json-language-features/client/src/browser/jsonClientMain.ts +++ b/extensions/json-language-features/client/src/browser/jsonClientMain.ts @@ -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); diff --git a/extensions/json-language-features/client/src/jsonClient.ts b/extensions/json-language-features/client/src/jsonClient.ts index 8fb6dda91e1..11c8c615299 100644 --- a/extensions/json-language-features/client/src/jsonClient.ts +++ b/extensions/json-language-features/client/src/jsonClient.ts @@ -6,6 +6,8 @@ import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); +export type JSONLanguageStatus = { schemas: string[] }; + import { workspace, window, languages, commands, ExtensionContext, extensions, Uri, Diagnostic, StatusBarAlignment, TextEditor, TextDocument, FormattingOptions, CancellationToken, @@ -18,7 +20,7 @@ import { } from 'vscode-languageclient'; import { hash } from './utils/hash'; -import { RequestService, joinPath } from './requests'; +import { createLanguageStatusItem } from './languageStatus'; namespace VSCodeContentRequest { export const type: RequestType = new RequestType('vscode/content'); @@ -32,6 +34,11 @@ namespace ForceValidateRequest { export const type: RequestType = new RequestType('json/validate'); } +namespace LanguageStatusRequest { + export const type: RequestType = new RequestType('json/languageStatus'); +} + + export interface ISchemaAssociations { [pattern: string]: string[]; } @@ -88,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; +} + +export const languageServerDescription = localize('jsonserver.name', 'JSON Language Server'); + export function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime) { const toDispose = context.subscriptions; @@ -190,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(); @@ -220,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 { @@ -314,6 +327,8 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua } }); + toDispose.push(createLanguageStatusItem(documentSelector, (uri: string) => client.sendRequest(LanguageStatusRequest.type, uri))); + function updateFormatterRegistration() { const formatEnabled = workspace.getConfiguration().get(SettingIds.enableFormatter); if (!formatEnabled && rangeFormatting) { @@ -376,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] === '%') { @@ -497,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; } diff --git a/extensions/json-language-features/client/src/languageStatus.ts b/extensions/json-language-features/client/src/languageStatus.ts new file mode 100644 index 00000000000..63541be773d --- /dev/null +++ b/extensions/json-language-features/client/src/languageStatus.ts @@ -0,0 +1,139 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +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): 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: ShowSchemasInput) => { + const items: ShowSchemasItem[] = arg.schemas.sort().map(renderShowSchemasItem); + const quickPick = window.createQuickPick(); + 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(() => { + commands.executeCommand('vscode.open', quickPick.selectedItems[0].uri); + quickPick.dispose(); + }); + }); + + const activeEditorListener = window.onDidChangeActiveTextEditor(() => { + updateLanguageStatus(); + }); + + async function updateLanguageStatus() { + const document = window.activeTextEditor?.document; + if (document && documentSelector.indexOf(document.languageId) !== -1) { + try { + statusItem.text = '$(loading~spin)'; + statusItem.detail = localize('pending.detail', 'Loading JSON info'); + statusItem.command = undefined; + + const schemas = (await statusRequest(document.uri.toString())).schemas; + statusItem.detail = undefined; + if (schemas.length === 0) { + statusItem.text = localize('status.noSchema', '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: 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, uri: document.uri.toString() } as ShowSchemasInput] + }; + } + } catch (e) { + statusItem.text = localize('status.error', 'Unable to compute used schemas'); + statusItem.detail = undefined; + statusItem.command = undefined; + console.log(e); + } + } else { + statusItem.text = localize('status.notJSON', 'Not a JSON editor'); + statusItem.detail = undefined; + statusItem.command = undefined; + } + } + + updateLanguageStatus(); + + return Disposable.from(statusItem, activeEditorListener, showSchemasCommand); +} + diff --git a/extensions/json-language-features/client/src/node/jsonClientMain.ts b/extensions/json-language-features/client/src/node/jsonClientMain.ts index b8f96f51753..186e7c8f7bf 100644 --- a/extensions/json-language-features/client/src/node/jsonClientMain.ts +++ b/extensions/json-language-features/client/src/node/jsonClientMain.ts @@ -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 { @@ -52,23 +59,88 @@ interface IPackageInfo { main: string; } -function getPackageInfo(context: ExtensionContext): IPackageInfo { +async function getPackageInfo(context: ExtensionContext): Promise { 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 { - 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 { + 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 => { + 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)); } }; } diff --git a/extensions/json-language-features/client/src/node/schemaCache.ts b/extensions/json-language-features/client/src/node/schemaCache.ts new file mode 100644 index 00000000000..4661c5c7a12 --- /dev/null +++ b/extensions/json-language-features/client/src/node/schemaCache.ts @@ -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(MEMENTO_KEY, {}); + } + + getETag(schemaUri: string): string | undefined { + return this.cacheInfo[schemaUri]?.etag; + } + + async putSchema(schemaUri: string, etag: string, schemaContent: string): Promise { + 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 { + 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 { + 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 { + 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 { + 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`; +} diff --git a/extensions/json-language-features/client/src/requests.ts b/extensions/json-language-features/client/src/requests.ts deleted file mode 100644 index ed06ded574f..00000000000 --- a/extensions/json-language-features/client/src/requests.ts +++ /dev/null @@ -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; -} - -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) }); -} diff --git a/extensions/json-language-features/client/tsconfig.json b/extensions/json-language-features/client/tsconfig.json index 959c5d37818..4254a37490e 100644 --- a/extensions/json-language-features/client/tsconfig.json +++ b/extensions/json-language-features/client/tsconfig.json @@ -6,6 +6,6 @@ "include": [ "src/**/*", "../../../src/vscode-dts/vscode.d.ts", - "../../../src/vscode-dts/vscode.proposed.d.ts" + "../../../src/vscode-dts/vscode.proposed.languageStatus.d.ts", ] } diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index 91dcbb8d554..984d0d2e76a 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -16,13 +16,15 @@ ], "main": "./client/out/node/jsonClientMain", "browser": "./client/dist/browser/jsonClientMain", - "enableProposedApi": true, "capabilities": { "virtualWorkspaces": true, "untrustedWorkspaces": { "supported": true } }, + "enabledApiProposals": [ + "languageStatus" + ], "scripts": { "compile": "npx gulp compile-extension:json-language-features-client compile-extension:json-language-features-server", "watch": "npx gulp watch-extension:json-language-features-client watch-extension:json-language-features-server", diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index cfbbe5e5abb..90ec7eb10d3 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -14,7 +14,7 @@ "dependencies": { "jsonc-parser": "^3.0.0", "request-light": "^0.5.4", - "vscode-json-languageservice": "^4.1.9", + "vscode-json-languageservice": "^4.2.0-next.1", "vscode-languageserver": "^7.0.0", "vscode-uri": "^3.0.2" }, diff --git a/extensions/json-language-features/server/src/jsonServer.ts b/extensions/json-language-features/server/src/jsonServer.ts index 2fcaa0a82e6..142594e0c3c 100644 --- a/extensions/json-language-features/server/src/jsonServer.ts +++ b/extensions/json-language-features/server/src/jsonServer.ts @@ -12,10 +12,12 @@ 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; +type JSONLanguageStatus = { schemas: string[] }; + namespace SchemaAssociationNotification { export const type: NotificationType = new NotificationType('json/schemaAssociations'); } @@ -36,14 +38,22 @@ namespace ForceValidateRequest { export const type: RequestType = new RequestType('json/validate'); } +namespace LanguageStatusRequest { + export const type: RequestType = new RequestType('json/languageStatus'); +} + 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; +} + export interface RuntimeEnvironment { file?: RequestService; http?: RequestService @@ -179,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; }; @@ -255,7 +265,11 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) // A schema has changed connection.onNotification(SchemaContentChangeNotification.type, uri => { - languageService.resetSchema(uri); + if (languageService.resetSchema(uri)) { + for (const doc of documents.all()) { + triggerValidation(doc); + } + } }); // Retry schema validation on all open documents @@ -273,6 +287,16 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); }); + connection.onRequest(LanguageStatusRequest.type, async uri => { + const document = documents.get(uri); + if (document) { + const jsonDocument = getJSONDocument(document); + return languageService.getLanguageStatus(document, jsonDocument); + } else { + return { schemas: [] }; + } + }); + function updateConfiguration() { const languageSettings = { validate: true, diff --git a/extensions/json-language-features/server/src/node/jsonServerMain.ts b/extensions/json-language-features/server/src/node/jsonServerMain.ts index 17e0fcd9940..4cb387095aa 100644 --- a/extensions/json-language-features/server/src/node/jsonServerMain.ts +++ b/extensions/json-language-features/server/src/node/jsonServerMain.ts @@ -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'; diff --git a/extensions/json-language-features/server/src/requests.ts b/extensions/json-language-features/server/src/requests.ts deleted file mode 100644 index a87cf92483f..00000000000 --- a/extensions/json-language-features/server/src/requests.ts +++ /dev/null @@ -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; -} - -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(); -} diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index 06fd41ef116..9b542a82f9c 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -22,10 +22,10 @@ request-light@^0.5.4: resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.4.tgz#497a98c6d8ae49536417a5e2d7f383b934f3e38c" integrity sha512-t3566CMweOFlUk7Y1DJMu5OrtpoZEb6aSTsLQVT3wtrIEJ5NhcY9G/Oqxvjllzl4a15zXfFlcr9q40LbLVQJqw== -vscode-json-languageservice@^4.1.9: - version "4.1.9" - resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-4.1.9.tgz#fb48edc69e37167c3cafd447c3fa898052d87b61" - integrity sha512-kxNHitUy2fCxmP6vAp0SRLrUSuecUYzzxlC+85cC3jJlFHWmvtCJOzikC+kcUnIdls9fQSB8n0yHs8Sl6taxJw== +vscode-json-languageservice@^4.2.0-next.1: + version "4.2.0-next.1" + resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-4.2.0-next.1.tgz#31a8c3be04c87d5aa593c11b98d84258b173a22f" + integrity sha512-aQvkkuZpeSPv86QLzyMdKTCgvXR+qSO39nSgj/XGaOcuHmTt7vMZB7ymYGGkQ4cAaQdHs/2G6a479LQybIGSbg== dependencies: jsonc-parser "^3.0.0" vscode-languageserver-textdocument "^1.0.1" diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index ba2157fde37..3734ba11aaf 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -5,7 +5,6 @@ "version": "1.0.0", "icon": "icon.png", "publisher": "vscode", - "enableProposedApi": true, "license": "MIT", "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", "engines": { diff --git a/extensions/markdown-language-features/src/test/documentLink.test.ts b/extensions/markdown-language-features/src/test/documentLink.test.ts index 8919f6a1ecf..7c280a82bdf 100644 --- a/extensions/markdown-language-features/src/test/documentLink.test.ts +++ b/extensions/markdown-language-features/src/test/documentLink.test.ts @@ -10,18 +10,26 @@ import { joinLines } from './util'; const testFileA = workspaceFile('a.md'); +const debug = false; + +function debugLog(...args: any[]) { + if (debug) { + console.log(...args); + } +} + function workspaceFile(...segments: string[]) { return vscode.Uri.joinPath(vscode.workspace.workspaceFolders![0].uri, ...segments); } async function getLinksForFile(file: vscode.Uri): Promise { - console.log('getting links', file.toString(), Date.now()); + debugLog('getting links', file.toString(), Date.now()); const r = (await vscode.commands.executeCommand('vscode.executeLinkProvider', file))!; - console.log('got links', file.toString(), Date.now()); + debugLog('got links', file.toString(), Date.now()); return r; } -suite('Markdown Document links', () => { +(vscode.env.uiKind === vscode.UIKind.Web ? suite.skip : suite)('Markdown Document links', () => { setup(async () => { // the tests make the assumption that link providers are already registered @@ -149,21 +157,21 @@ function assertActiveDocumentUri(expectedUri: vscode.Uri) { } async function withFileContents(file: vscode.Uri, contents: string): Promise { - console.log('openTextDocument', file.toString(), Date.now()); + debugLog('openTextDocument', file.toString(), Date.now()); const document = await vscode.workspace.openTextDocument(file); - console.log('showTextDocument', file.toString(), Date.now()); + debugLog('showTextDocument', file.toString(), Date.now()); const editor = await vscode.window.showTextDocument(document); - console.log('editTextDocument', file.toString(), Date.now()); + debugLog('editTextDocument', file.toString(), Date.now()); await editor.edit(edit => { edit.replace(new vscode.Range(0, 0, 1000, 0), contents); }); - console.log('opened done', vscode.window.activeTextEditor?.document.toString(), Date.now()); + debugLog('opened done', vscode.window.activeTextEditor?.document.toString(), Date.now()); } async function executeLink(link: vscode.DocumentLink) { - console.log('executeingLink', link.target?.toString(), Date.now()); + debugLog('executeingLink', link.target?.toString(), Date.now()); const args = JSON.parse(decodeURIComponent(link.target!.query)); await vscode.commands.executeCommand(link.target!.path, args); - console.log('executedLink', vscode.window.activeTextEditor?.document.toString(), Date.now()); + debugLog('executedLink', vscode.window.activeTextEditor?.document.toString(), Date.now()); } diff --git a/extensions/markdown-language-features/tsconfig.json b/extensions/markdown-language-features/tsconfig.json index ecbf24dde1e..fcd79775de5 100644 --- a/extensions/markdown-language-features/tsconfig.json +++ b/extensions/markdown-language-features/tsconfig.json @@ -5,7 +5,6 @@ }, "include": [ "src/**/*", - "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.d.ts" + "../../src/vscode-dts/vscode.d.ts" ] } diff --git a/extensions/microsoft-authentication/package.json b/extensions/microsoft-authentication/package.json index ef97ae07a9b..33e5eb749fc 100644 --- a/extensions/microsoft-authentication/package.json +++ b/extensions/microsoft-authentication/package.json @@ -12,7 +12,6 @@ "categories": [ "Other" ], - "enableProposedApi": true, "activationEvents": [ "onAuthenticationRequest:microsoft" ], diff --git a/extensions/microsoft-authentication/tsconfig.json b/extensions/microsoft-authentication/tsconfig.json index c340ac241e9..51edc522bc6 100644 --- a/extensions/microsoft-authentication/tsconfig.json +++ b/extensions/microsoft-authentication/tsconfig.json @@ -18,7 +18,6 @@ ], "include": [ "src/**/*", - "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.d.ts" + "../../src/vscode-dts/vscode.d.ts" ] } diff --git a/extensions/npm/package.json b/extensions/npm/package.json index 3e85fc13ae1..e3baafd8bfe 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -8,7 +8,6 @@ "engines": { "vscode": "0.10.x" }, - "enableProposedApi": true, "icon": "images/npm_icon.png", "categories": [ "Other" @@ -73,7 +72,8 @@ "name": "%view.name%", "when": "npm:showScriptExplorer", "icon": "$(json)", - "visibility": "hidden" + "visibility": "hidden", + "contextualTitle": "%view.name%" } ] }, @@ -281,6 +281,15 @@ "scope": "window", "default": "open" }, + "npm.scriptExplorerExclude": { + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "%config.npm.scriptExplorerExclude%", + "scope": "resource", + "default": [] + }, "npm.fetchOnlinePackageInfo": { "type": "boolean", "description": "%config.npm.fetchOnlinePackageInfo%", diff --git a/extensions/npm/package.nls.json b/extensions/npm/package.nls.json index b3ff9a4d229..b0a4f06c01f 100644 --- a/extensions/npm/package.nls.json +++ b/extensions/npm/package.nls.json @@ -12,6 +12,7 @@ "config.npm.exclude": "Configure glob patterns for folders that should be excluded from automatic script detection.", "config.npm.enableScriptExplorer": "Enable an explorer view for npm scripts when there is no top-level 'package.json' file.", "config.npm.scriptExplorerAction": "The default click action used in the npm scripts explorer: `open` or `run`, the default is `open`.", + "config.npm.scriptExplorerExclude": "An array of regular expressions that indicate which scripts should be excluded from the NPM Scripts view.", "config.npm.enableRunFromFolder": "Enable running npm scripts contained in a folder from the Explorer context menu.", "config.npm.fetchOnlinePackageInfo": "Fetch data from https://registry.npmjs.org and https://registry.bower.io to provide auto-completion and information on hover features on npm dependencies.", "npm.parseError": "Npm task detection: failed to parse the file {0}", diff --git a/extensions/npm/src/npmMain.ts b/extensions/npm/src/npmMain.ts index 6969b7d63a6..3788c94ea45 100644 --- a/extensions/npm/src/npmMain.ts +++ b/extensions/npm/src/npmMain.ts @@ -38,7 +38,7 @@ export async function activate(context: vscode.ExtensionContext): Promise treeDataProvider = registerExplorer(context); context.subscriptions.push(vscode.workspace.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration('npm.exclude') || e.affectsConfiguration('npm.autoDetect')) { + if (e.affectsConfiguration('npm.exclude') || e.affectsConfiguration('npm.autoDetect') || e.affectsConfiguration('npm.scriptExplorerExclude')) { invalidateTasksCache(); if (treeDataProvider) { treeDataProvider.refresh(); diff --git a/extensions/npm/src/npmView.ts b/extensions/npm/src/npmView.ts index 7f06a994f70..47b43c8f74e 100644 --- a/extensions/npm/src/npmView.ts +++ b/extensions/npm/src/npmView.ts @@ -291,7 +291,14 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider { let folder = null; let packageJson = null; + const regularExpressionsSetting = workspace.getConfiguration('npm').get('scriptExplorerExclude', []); + const regularExpressions = regularExpressionsSetting?.map(value => RegExp(value)); + tasks.forEach(each => { + if (regularExpressions.some((regularExpression) => (each.task.definition).script.match(regularExpression))) { + return; + } + if (isWorkspaceFolder(each.task.scope) && !this.isInstallTask(each.task)) { folder = folders.get(each.task.scope.name); if (!folder) { diff --git a/extensions/npm/tsconfig.json b/extensions/npm/tsconfig.json index 980445e09b2..7234fdfeb97 100644 --- a/extensions/npm/tsconfig.json +++ b/extensions/npm/tsconfig.json @@ -8,7 +8,6 @@ }, "include": [ "src/**/*", - "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.d.ts" + "../../src/vscode-dts/vscode.d.ts" ] } diff --git a/extensions/package.json b/extensions/package.json index 22172c71c86..2c5746b4e73 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -4,7 +4,7 @@ "license": "MIT", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "^4.5.1-rc" + "typescript": "4.5" }, "scripts": { "postinstall": "node ./postinstall" diff --git a/extensions/php-language-features/package.json b/extensions/php-language-features/package.json index 8447f019786..03176a05573 100644 --- a/extensions/php-language-features/package.json +++ b/extensions/php-language-features/package.json @@ -9,7 +9,6 @@ "engines": { "vscode": "0.10.x" }, - "enableProposedApi": true, "activationEvents": [ "onLanguage:php" ], diff --git a/extensions/php-language-features/tsconfig.json b/extensions/php-language-features/tsconfig.json index 980445e09b2..7234fdfeb97 100644 --- a/extensions/php-language-features/tsconfig.json +++ b/extensions/php-language-features/tsconfig.json @@ -8,7 +8,6 @@ }, "include": [ "src/**/*", - "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.d.ts" + "../../src/vscode-dts/vscode.d.ts" ] } diff --git a/extensions/search-result/package.json b/extensions/search-result/package.json index d7f9aa4a138..747cde8e648 100644 --- a/extensions/search-result/package.json +++ b/extensions/search-result/package.json @@ -3,7 +3,6 @@ "displayName": "%displayName%", "description": "%description%", "version": "1.0.0", - "enableProposedApi": true, "publisher": "vscode", "license": "MIT", "icon": "images/icon.png", @@ -28,6 +27,9 @@ "supported": true } }, + "enabledApiProposals": [ + "documentFiltersExclusive" + ], "contributes": { "configurationDefaults": { "[search-result]": { diff --git a/extensions/search-result/tsconfig.json b/extensions/search-result/tsconfig.json index bd7e74d1d83..f0f7c00adf5 100644 --- a/extensions/search-result/tsconfig.json +++ b/extensions/search-result/tsconfig.json @@ -5,7 +5,6 @@ }, "include": [ "src/**/*", - "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.d.ts" + "../../src/vscode-dts/vscode.d.ts" ] } diff --git a/extensions/simple-browser/package.json b/extensions/simple-browser/package.json index a1b2560bbc9..ea0fd90265b 100644 --- a/extensions/simple-browser/package.json +++ b/extensions/simple-browser/package.json @@ -2,7 +2,9 @@ "name": "simple-browser", "displayName": "%displayName%", "description": "%description%", - "enableProposedApi": true, + "enabledApiProposals": [ + "externalUriOpener" + ], "version": "1.0.0", "icon": "media/icon.png", "publisher": "vscode", diff --git a/extensions/simple-browser/tsconfig.json b/extensions/simple-browser/tsconfig.json index c9980a21e38..bd370826678 100644 --- a/extensions/simple-browser/tsconfig.json +++ b/extensions/simple-browser/tsconfig.json @@ -7,6 +7,6 @@ "include": [ "src/**/*", "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.d.ts" + "../../src/vscode-dts/vscode.proposed.externalUriOpener.d.ts", ] } diff --git a/extensions/theme-solarized-light/themes/solarized-light-color-theme.json b/extensions/theme-solarized-light/themes/solarized-light-color-theme.json index dfa65c9411a..cfb65d06c78 100644 --- a/extensions/theme-solarized-light/themes/solarized-light-color-theme.json +++ b/extensions/theme-solarized-light/themes/solarized-light-color-theme.json @@ -355,6 +355,7 @@ // "scrollbarSlider.hoverBackground": "", // Editor "editor.background": "#FDF6E3", + "notebook.cellEditorBackground": "#F7F0E0", // "editor.foreground": "#6688cc", "editorWidget.background": "#EEE8D5", "editorCursor.foreground": "#657B83", diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 280a5fe744e..6651308b424 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -7,9 +7,12 @@ "publisher": "vscode", "license": "MIT", "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", - "enableProposedApi": true, "enabledApiProposals": [ - "languageStatus" + "inlayHints", + "languageStatus", + "quickPickSeparators", + "resolvers", + "workspaceTrust" ], "capabilities": { "virtualWorkspaces": { @@ -1113,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" } } diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index 0014fe26992..4f76591ad37 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -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" } diff --git a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts index 369136f378b..c79e44c63eb 100644 --- a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts +++ b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts @@ -186,7 +186,7 @@ export default class FileConfigurationManager extends Disposable { generateReturnInDocTemplate: config.get('suggest.jsdoc.generateReturns', true), includeCompletionsForImportStatements: config.get('suggest.includeCompletionsForImportStatements', true), includeCompletionsWithSnippetText: config.get('suggest.includeCompletionsWithSnippetText', true), - includeCompletionsWithClassMemberSnippets: config.get('suggest.includeCompletionsWithClassMemberSnippets', true), + includeCompletionsWithClassMemberSnippets: config.get('suggest.classMemberSnippets.enabled', true), allowIncompleteCompletions: true, displayPartsForJSDoc: true, ...getInlayHintsPreferences(config), diff --git a/extensions/typescript-language-features/src/tsServer/spawner.ts b/extensions/typescript-language-features/src/tsServer/spawner.ts index 6dcf81d929d..9a1e767bdbc 100644 --- a/extensions/typescript-language-features/src/tsServer/spawner.ts +++ b/extensions/typescript-language-features/src/tsServer/spawner.ts @@ -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 }; } diff --git a/extensions/typescript-language-features/src/tsServer/versionManager.ts b/extensions/typescript-language-features/src/tsServer/versionManager.ts index efd567148dd..7b8a3640660 100644 --- a/extensions/typescript-language-features/src/tsServer/versionManager.ts +++ b/extensions/typescript-language-features/src/tsServer/versionManager.ts @@ -82,6 +82,11 @@ export class TypeScriptVersionManager extends Disposable { const selected = await vscode.window.showQuickPick([ 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')); diff --git a/extensions/typescript-language-features/src/typeScriptServiceClientHost.ts b/extensions/typescript-language-features/src/typeScriptServiceClientHost.ts index 41d06ed0576..f67db7f3858 100644 --- a/extensions/typescript-language-features/src/typeScriptServiceClientHost.ts +++ b/extensions/typescript-language-features/src/typeScriptServiceClientHost.ts @@ -62,7 +62,7 @@ export default class TypeScriptServiceClientHost extends Disposable { constructor( descriptions: LanguageDescription[], context: vscode.ExtensionContext, - onCaseInsenitiveFileSystem: boolean, + onCaseInsensitiveFileSystem: boolean, services: { pluginManager: PluginManager, commandManager: CommandManager, @@ -82,7 +82,7 @@ export default class TypeScriptServiceClientHost extends Disposable { const allModeIds = this.getAllModeIds(descriptions, services.pluginManager); this.client = this._register(new TypeScriptServiceClient( context, - onCaseInsenitiveFileSystem, + onCaseInsensitiveFileSystem, services, allModeIds)); @@ -99,7 +99,7 @@ export default class TypeScriptServiceClientHost extends Disposable { this.typingsStatus = this._register(new TypingsStatus(this.client)); this._register(LargeProjectStatus.create(this.client)); - this.fileConfigurationManager = this._register(new FileConfigurationManager(this.client, onCaseInsenitiveFileSystem)); + this.fileConfigurationManager = this._register(new FileConfigurationManager(this.client, onCaseInsensitiveFileSystem)); for (const description of descriptions) { const manager = new LanguageProvider(this.client, description, this.commandManager, this.client.telemetryReporter, this.typingsStatus, this.fileConfigurationManager, onCompletionAccepted); diff --git a/extensions/typescript-language-features/src/utils/previewer.ts b/extensions/typescript-language-features/src/utils/previewer.ts index a443efba037..c6d1c942050 100644 --- a/extensions/typescript-language-features/src/utils/previewer.ts +++ b/extensions/typescript-language-features/src/utils/previewer.ts @@ -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( diff --git a/extensions/typescript-language-features/tsconfig.json b/extensions/typescript-language-features/tsconfig.json index 9eac9f6421f..757f9ec5984 100644 --- a/extensions/typescript-language-features/tsconfig.json +++ b/extensions/typescript-language-features/tsconfig.json @@ -10,7 +10,10 @@ "include": [ "src/**/*", "../../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.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", ] } diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index dcdd8b608a9..45de8bf3b0a 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -4,7 +4,55 @@ "version": "0.0.1", "publisher": "vscode", "license": "MIT", - "enableProposedApi": true, + "enabledApiProposals": [ + "authSession", + "contribViewsRemote", + "customEditorMove", + "diffCommand", + "documentFiltersExclusive", + "editorInsets", + "extensionRuntime", + "externalUriOpener", + "fileSearchProvider", + "findTextInFiles", + "fsChunks", + "inlayHints", + "inlineCompletions", + "languageStatus", + "notebookCellExecutionState", + "notebookConcatTextDocument", + "notebookContentProvider", + "notebookControllerKind", + "notebookDebugOptions", + "notebookDeprecated", + "notebookEditor", + "notebookEditorDecorationType", + "notebookEditorEdit", + "notebookLiveShare", + "notebookMessaging", + "notebookMime", + "portsAttributes", + "quickPickSortByLabel", + "resolvers", + "scmActionButton", + "scmSelectedProvider", + "scmValidation", + "tabs", + "taskPresentationGroup", + "terminalDataWriteEvent", + "terminalDimensions", + "terminalLocation", + "terminalNameChangeEvent", + "testCoverage", + "testObserver", + "textDocumentNotebook", + "textSearchProvider", + "timeline", + "tokenInformation", + "treeViewDragAndDrop", + "treeViewReveal", + "workspaceTrust" + ], "private": true, "activationEvents": [], "main": "./out/extension", diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts index ba7ce21e32f..bf0da5076b1 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts @@ -11,6 +11,7 @@ interface QuickPickExpected { events: string[]; activeItems: string[][]; selectionItems: string[][]; + values: string[]; acceptedItems: { active: string[][]; selection: string[][]; @@ -18,6 +19,15 @@ interface QuickPickExpected { }; } +interface InputBoxExpected { + events: string[]; + values: string[]; + accepted: { + values: string[]; + dispose: boolean[]; + }; +} + suite('vscode API - quick input', function () { teardown(async function () { @@ -35,6 +45,7 @@ suite('vscode API - quick input', function () { events: ['active', 'active', 'selection', 'accept', 'hide'], activeItems: [['eins'], ['zwei']], selectionItems: [['zwei']], + values: [], acceptedItems: { active: [['zwei']], selection: [['zwei']], @@ -61,6 +72,7 @@ suite('vscode API - quick input', function () { events: ['active', 'selection', 'accept', 'hide'], activeItems: [['zwei']], selectionItems: [['zwei']], + values: [], acceptedItems: { active: [['zwei']], selection: [['zwei']], @@ -87,6 +99,7 @@ suite('vscode API - quick input', function () { events: ['active', 'selection', 'active', 'selection', 'accept', 'hide'], activeItems: [['eins'], ['zwei']], selectionItems: [['eins'], ['eins', 'zwei']], + values: [], acceptedItems: { active: [['zwei']], selection: [['eins', 'zwei']], @@ -117,6 +130,7 @@ suite('vscode API - quick input', function () { events: ['active', 'selection', 'accept', 'selection', 'accept', 'hide'], activeItems: [['eins']], selectionItems: [['zwei'], ['drei']], + values: [], acceptedItems: { active: [['eins'], ['eins']], selection: [['zwei'], ['drei']], @@ -142,6 +156,7 @@ suite('vscode API - quick input', function () { events: ['active', 'selection', 'accept', 'active', 'selection', 'active', 'selection', 'accept', 'hide'], activeItems: [['eins'], [], ['drei']], selectionItems: [['eins'], [], ['drei']], + values: [], acceptedItems: { active: [['eins'], ['drei']], selection: [['eins'], ['drei']], @@ -163,6 +178,40 @@ suite('vscode API - quick input', function () { .catch(err => done(err)); }); + // NOTE: This test is currently accepting the wrong behavior of #135971 + // so that we can test the fix for #137279. + test('createQuickPick, onDidChangeValue gets triggered', function (_done) { + let done = (err?: any) => { + done = () => { }; + _done(err); + }; + + const quickPick = createQuickPick({ + events: ['active', 'active', 'active', 'active', 'value', 'active', 'active', 'value', 'hide'], + activeItems: [['eins'], ['zwei'], [], ['zwei'], [], ['eins']], + selectionItems: [], + values: ['zwei', ''], + acceptedItems: { + active: [], + selection: [], + dispose: [] + }, + }, (err?: any) => done(err)); + quickPick.items = ['eins', 'zwei'].map(label => ({ label })); + quickPick.show(); + + (async () => { + quickPick.value = 'zwei'; + await timeout(async () => { + quickPick.value = ''; + await timeout(async () => { + quickPick.hide(); + }, 0); + }, 0); + })() + .catch(err => done(err)); + }); + test('createQuickPick, dispose in onDidHide', function (_done) { let done = (err?: any) => { done = () => { }; @@ -248,6 +297,34 @@ suite('vscode API - quick input', function () { quickPick.hide(); await waitForHide(quickPick); }); + + test('createInputBox, onDidChangeValue gets triggered', function (_done) { + let done = (err?: any) => { + done = () => { }; + _done(err); + }; + + const quickPick = createInputBox({ + events: ['value', 'accept', 'hide'], + values: ['zwei'], + accepted: { + values: ['zwei'], + dispose: [true] + }, + }, (err?: any) => done(err)); + quickPick.show(); + + (async () => { + quickPick.value = 'zwei'; + await timeout(async () => { + await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); + await timeout(async () => { + quickPick.hide(); + }, 0); + }, 0); + })() + .catch(err => done(err)); + }); }); function createQuickPick(expected: QuickPickExpected, done: (err?: any) => void, record = false) { @@ -316,9 +393,78 @@ function createQuickPick(expected: QuickPickExpected, done: (err?: any) => void, } }); + quickPick.onDidChangeValue(value => { + if (record) { + console.log('value'); + return; + } + + try { + eventIndex++; + assert.strictEqual('value', expected.events.shift(), `onDidChangeValue (event ${eventIndex})`); + const expectedValue = expected.values.shift(); + assert.deepStrictEqual(value, expectedValue, `onDidChangeValue event value (event ${eventIndex})`); + } catch (err) { + done(err); + } + }); + return quickPick; } +function createInputBox(expected: InputBoxExpected, done: (err?: any) => void, record = false) { + const inputBox = window.createInputBox(); + let eventIndex = -1; + inputBox.onDidAccept(() => { + if (record) { + console.log('accept'); + return; + } + try { + eventIndex++; + assert.strictEqual('accept', expected.events.shift(), `onDidAccept (event ${eventIndex})`); + const expectedValue = expected.accepted.values.shift(); + assert.deepStrictEqual(inputBox.value, expectedValue, `onDidAccept event value (event ${eventIndex})`); + if (expected.accepted.dispose.shift()) { + inputBox.dispose(); + } + } catch (err) { + done(err); + } + }); + inputBox.onDidHide(() => { + if (record) { + console.log('hide'); + done(); + return; + } + try { + assert.strictEqual('hide', expected.events.shift()); + done(); + } catch (err) { + done(err); + } + }); + + inputBox.onDidChangeValue(value => { + if (record) { + console.log('value'); + return; + } + + try { + eventIndex++; + assert.strictEqual('value', expected.events.shift(), `onDidChangeValue (event ${eventIndex})`); + const expectedValue = expected.values.shift(); + assert.deepStrictEqual(value, expectedValue, `onDidChangeValue event value (event ${eventIndex})`); + } catch (err) { + done(err); + } + }); + + return inputBox; +} + async function timeout(run: () => Promise | T, ms: number): Promise { return new Promise(resolve => setTimeout(() => resolve(run()), ms)); } diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts index c88fa9619d6..2bb71edea11 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -425,23 +425,24 @@ import { assertNoRpc } from '../utils'; }); suite('Extension pty terminals', () => { - test('should fire onDidOpenTerminal and onDidCloseTerminal', (done) => { - disposables.push(window.onDidOpenTerminal(term => { - try { - equal(term.name, 'c'); - } catch (e) { - done(e); - return; - } - disposables.push(window.onDidCloseTerminal(() => done())); - term.dispose(); - })); + test('should fire onDidOpenTerminal and onDidCloseTerminal', async () => { const pty: Pseudoterminal = { onDidWrite: new EventEmitter().event, open: () => { }, close: () => { } }; - window.createTerminal({ name: 'c', pty }); + const terminal = await new Promise(r => { + disposables.push(window.onDidOpenTerminal(t => { + if (t.name === 'c') { + r(t); + } + })); + window.createTerminal({ name: 'c', pty }); + }); + await new Promise(r => { + disposables.push(window.onDidCloseTerminal(() => r())); + terminal.dispose(); + }); }); // The below tests depend on global UI state and each other @@ -491,31 +492,8 @@ import { assertNoRpc } from '../utils'; // const terminal = window.createTerminal({ name: 'foo', pty }); // }); - test('should respect dimension overrides', (done) => { - disposables.push(window.onDidOpenTerminal(term => { - try { - equal(terminal, term); - } catch (e) { - done(e); - return; - } - term.show(); - disposables.push(window.onDidChangeTerminalDimensions(e => { - // The default pty dimensions have a chance to appear here since override - // dimensions happens after the terminal is created. If so just ignore and - // wait for the right dimensions - if (e.dimensions.columns === 10 || e.dimensions.rows === 5) { - try { - equal(e.terminal, terminal); - } catch (e) { - done(e); - return; - } - disposables.push(window.onDidCloseTerminal(() => done())); - terminal.dispose(); - } - })); - })); + // TODO: Fix test, flaky in CI (local and remote) https://github.com/microsoft/vscode/issues/137155 + test.skip('should respect dimension overrides', async () => { const writeEmitter = new EventEmitter(); const overrideDimensionsEmitter = new EventEmitter(); const pty: Pseudoterminal = { @@ -524,29 +502,38 @@ import { assertNoRpc } from '../utils'; open: () => overrideDimensionsEmitter.fire({ columns: 10, rows: 5 }), close: () => { } }; - const terminal = window.createTerminal({ name: 'foo', pty }); + const terminal = await new Promise(r => { + disposables.push(window.onDidOpenTerminal(t => { + if (t === created) { + r(t); + } + })); + const created = window.createTerminal({ name: 'foo', pty }); + }); + // Exit the test early if dimensions already match which may happen if the exthost + // has high latency + if (terminal.dimensions?.columns === 10 && terminal.dimensions?.rows === 5) { + return; + } + // TODO: Remove logs when the test is verified as non-flaky + await new Promise(r => { + // Does this never fire because it's already set to 10x5? + disposables.push(window.onDidChangeTerminalDimensions(e => { + console.log(`window.onDidChangeTerminalDimensions event, dimensions = ${e.dimensions?.columns}x${e.dimensions?.rows}`); + // The default pty dimensions have a chance to appear here since override + // dimensions happens after the terminal is created. If so just ignore and + // wait for the right dimensions + if (e.terminal === terminal && e.dimensions.columns === 10 && e.dimensions.rows === 5) { + disposables.push(window.onDidCloseTerminal(() => r())); + terminal.dispose(); + } + })); + console.log(`listening for window.onDidChangeTerminalDimensions, current dimensions = ${terminal.dimensions?.columns}x${terminal.dimensions?.rows}`); + terminal.show(); + }); }); - test('should change terminal name', (done) => { - disposables.push(window.onDidOpenTerminal(term => { - try { - equal(terminal, term); - equal(terminal.name, 'foo'); - } catch (e) { - done(e); - return; - } - disposables.push(window.onDidCloseTerminal(t => { - try { - equal(terminal, t); - equal(terminal.name, 'bar'); - } catch (e) { - done(e); - return; - } - done(); - })); - })); + test('should change terminal name', async () => { const changeNameEmitter = new EventEmitter(); const closeEmitter = new EventEmitter(); const pty: Pseudoterminal = { @@ -559,29 +546,22 @@ import { assertNoRpc } from '../utils'; }, close: () => { } }; - const terminal = window.createTerminal({ name: 'foo', pty }); + await new Promise(r => { + disposables.push(window.onDidOpenTerminal(t1 => { + if (t1 === created) { + disposables.push(window.onDidCloseTerminal(t2 => { + if (t2 === created) { + strictEqual(t1.name, 'bar'); + r(); + } + })); + } + })); + const created = window.createTerminal({ name: 'foo', pty }); + }); }); - test('exitStatus.code should be set to the exit code (undefined)', (done) => { - disposables.push(window.onDidOpenTerminal(term => { - try { - equal(terminal, term); - equal(terminal.exitStatus, undefined); - } catch (e) { - done(e); - return; - } - disposables.push(window.onDidCloseTerminal(t => { - try { - equal(terminal, t); - deepEqual(terminal.exitStatus, { code: undefined }); - } catch (e) { - done(e); - return; - } - done(); - })); - })); + test('exitStatus.code should be set to the exit code (undefined)', async () => { const writeEmitter = new EventEmitter(); const closeEmitter = new EventEmitter(); const pty: Pseudoterminal = { @@ -590,29 +570,23 @@ import { assertNoRpc } from '../utils'; open: () => closeEmitter.fire(undefined), close: () => { } }; - const terminal = window.createTerminal({ name: 'foo', pty }); + await new Promise(r => { + disposables.push(window.onDidOpenTerminal(t1 => { + if (t1 === created) { + strictEqual(created.exitStatus, undefined); + disposables.push(window.onDidCloseTerminal(t2 => { + if (t2 === created) { + deepStrictEqual(created.exitStatus, { code: undefined }); + r(); + } + })); + } + })); + const created = window.createTerminal({ name: 'foo', pty }); + }); }); - test('exitStatus.code should be set to the exit code (zero)', (done) => { - disposables.push(window.onDidOpenTerminal(term => { - try { - equal(terminal, term); - equal(terminal.exitStatus, undefined); - } catch (e) { - done(e); - return; - } - disposables.push(window.onDidCloseTerminal(t => { - try { - equal(terminal, t); - deepEqual(terminal.exitStatus, { code: 0 }); - } catch (e) { - done(e); - return; - } - done(); - })); - })); + test('exitStatus.code should be set to the exit code (zero)', async () => { const writeEmitter = new EventEmitter(); const closeEmitter = new EventEmitter(); const pty: Pseudoterminal = { @@ -621,29 +595,23 @@ import { assertNoRpc } from '../utils'; open: () => closeEmitter.fire(0), close: () => { } }; - const terminal = window.createTerminal({ name: 'foo', pty }); + await new Promise(r => { + disposables.push(window.onDidOpenTerminal(t1 => { + if (t1 === created) { + strictEqual(created.exitStatus, undefined); + disposables.push(window.onDidCloseTerminal(t2 => { + if (t2 === created) { + deepStrictEqual(created.exitStatus, { code: 0 }); + r(); + } + })); + } + })); + const created = window.createTerminal({ name: 'foo', pty }); + }); }); - test('exitStatus.code should be set to the exit code (non-zero)', (done) => { - disposables.push(window.onDidOpenTerminal(term => { - try { - equal(terminal, term); - equal(terminal.exitStatus, undefined); - } catch (e) { - done(e); - return; - } - disposables.push(window.onDidCloseTerminal(t => { - try { - equal(terminal, t); - deepEqual(terminal.exitStatus, { code: 22 }); - } catch (e) { - done(e); - return; - } - done(); - })); - })); + test('exitStatus.code should be set to the exit code (non-zero)', async () => { const writeEmitter = new EventEmitter(); const closeEmitter = new EventEmitter(); const pty: Pseudoterminal = { @@ -657,20 +625,23 @@ import { assertNoRpc } from '../utils'; }, close: () => { } }; - const terminal = window.createTerminal({ name: 'foo', pty }); + await new Promise(r => { + disposables.push(window.onDidOpenTerminal(t1 => { + if (t1 === created) { + strictEqual(created.exitStatus, undefined); + disposables.push(window.onDidCloseTerminal(t2 => { + if (t2 === created) { + deepStrictEqual(created.exitStatus, { code: 22 }); + r(); + } + })); + } + })); + const created = window.createTerminal({ name: 'foo', pty }); + }); }); - test('creationOptions should be set and readonly for ExtensionTerminalOptions terminals', (done) => { - disposables.push(window.onDidOpenTerminal(term => { - try { - equal(terminal, term); - } catch (e) { - done(e); - return; - } - terminal.dispose(); - disposables.push(window.onDidCloseTerminal(() => done())); - })); + test('creationOptions should be set and readonly for ExtensionTerminalOptions terminals', async () => { const writeEmitter = new EventEmitter(); const pty: Pseudoterminal = { onDidWrite: writeEmitter.event, @@ -678,16 +649,20 @@ import { assertNoRpc } from '../utils'; close: () => { } }; const options = { name: 'foo', pty }; - const terminal = window.createTerminal(options); - try { - equal(terminal.name, 'foo'); + await new Promise(r => { + disposables.push(window.onDidOpenTerminal(term => { + if (term === terminal) { + terminal.dispose(); + disposables.push(window.onDidCloseTerminal(() => r())); + } + })); + const terminal = window.createTerminal(options); + strictEqual(terminal.name, 'foo'); const terminalOptions = terminal.creationOptions as ExtensionTerminalOptions; - equal(terminalOptions.name, 'foo'); - equal(terminalOptions.pty, pty); + strictEqual(terminalOptions.name, 'foo'); + strictEqual(terminalOptions.pty, pty); throws(() => terminalOptions.name = 'bad', 'creationOptions should be readonly at runtime'); - } catch (e) { - done(e); - } + }); }); }); diff --git a/extensions/vscode-api-tests/testWorkspace/image%.png b/extensions/vscode-api-tests/testWorkspace/image%.png index 15b462975be..761522a3347 100644 Binary files a/extensions/vscode-api-tests/testWorkspace/image%.png and b/extensions/vscode-api-tests/testWorkspace/image%.png differ diff --git a/extensions/vscode-api-tests/testWorkspace/image%02.png b/extensions/vscode-api-tests/testWorkspace/image%02.png index 15b462975be..761522a3347 100644 Binary files a/extensions/vscode-api-tests/testWorkspace/image%02.png and b/extensions/vscode-api-tests/testWorkspace/image%02.png differ diff --git a/extensions/vscode-api-tests/testWorkspace/image.png b/extensions/vscode-api-tests/testWorkspace/image.png index 15b462975be..761522a3347 100644 Binary files a/extensions/vscode-api-tests/testWorkspace/image.png and b/extensions/vscode-api-tests/testWorkspace/image.png differ diff --git a/extensions/vscode-api-tests/tsconfig.json b/extensions/vscode-api-tests/tsconfig.json index 980445e09b2..3ef85d919ec 100644 --- a/extensions/vscode-api-tests/tsconfig.json +++ b/extensions/vscode-api-tests/tsconfig.json @@ -9,6 +9,6 @@ "include": [ "src/**/*", "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.d.ts" + "../../src/vscode-dts/vscode.proposed.*.d.ts" ] } diff --git a/extensions/vscode-colorize-tests/package.json b/extensions/vscode-colorize-tests/package.json index b2ce2e9d9cf..b27439bf8b6 100644 --- a/extensions/vscode-colorize-tests/package.json +++ b/extensions/vscode-colorize-tests/package.json @@ -9,7 +9,6 @@ "onLanguage:json" ], "main": "./out/colorizerTestMain", - "enableProposedApi": true, "engines": { "vscode": "*" }, diff --git a/extensions/git/test/colorize-fixtures/COMMIT_EDITMSG b/extensions/vscode-colorize-tests/test/colorize-fixtures/COMMIT_EDITMSG similarity index 100% rename from extensions/git/test/colorize-fixtures/COMMIT_EDITMSG rename to extensions/vscode-colorize-tests/test/colorize-fixtures/COMMIT_EDITMSG diff --git a/extensions/git/test/colorize-fixtures/git-rebase-todo b/extensions/vscode-colorize-tests/test/colorize-fixtures/git-rebase-todo similarity index 89% rename from extensions/git/test/colorize-fixtures/git-rebase-todo rename to extensions/vscode-colorize-tests/test/colorize-fixtures/git-rebase-todo index 3b6df1cd4f7..3efe501eb98 100644 --- a/extensions/git/test/colorize-fixtures/git-rebase-todo +++ b/extensions/vscode-colorize-tests/test/colorize-fixtures/git-rebase-todo @@ -12,4 +12,4 @@ reword 4ca2acc i cant' typ goods # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message -# x, exec = run command (the rest of the line) using shell \ No newline at end of file +# x, exec = run command (the rest of the line) using shell diff --git a/extensions/git/test/colorize-fixtures/example.diff b/extensions/vscode-colorize-tests/test/colorize-fixtures/test.diff similarity index 100% rename from extensions/git/test/colorize-fixtures/example.diff rename to extensions/vscode-colorize-tests/test/colorize-fixtures/test.diff diff --git a/extensions/git/test/colorize-results/COMMIT_EDITMSG.json b/extensions/vscode-colorize-tests/test/colorize-results/COMMIT_EDITMSG.json similarity index 100% rename from extensions/git/test/colorize-results/COMMIT_EDITMSG.json rename to extensions/vscode-colorize-tests/test/colorize-results/COMMIT_EDITMSG.json diff --git a/extensions/git/test/colorize-results/git-rebase-todo.json b/extensions/vscode-colorize-tests/test/colorize-results/git-rebase-todo.json similarity index 100% rename from extensions/git/test/colorize-results/git-rebase-todo.json rename to extensions/vscode-colorize-tests/test/colorize-results/git-rebase-todo.json diff --git a/extensions/git/test/colorize-results/example_diff.json b/extensions/vscode-colorize-tests/test/colorize-results/test_diff.json similarity index 100% rename from extensions/git/test/colorize-results/example_diff.json rename to extensions/vscode-colorize-tests/test/colorize-results/test_diff.json diff --git a/extensions/vscode-colorize-tests/tsconfig.json b/extensions/vscode-colorize-tests/tsconfig.json index 980445e09b2..7234fdfeb97 100644 --- a/extensions/vscode-colorize-tests/tsconfig.json +++ b/extensions/vscode-colorize-tests/tsconfig.json @@ -8,7 +8,6 @@ }, "include": [ "src/**/*", - "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.d.ts" + "../../src/vscode-dts/vscode.d.ts" ] } diff --git a/extensions/vscode-custom-editor-tests/package.json b/extensions/vscode-custom-editor-tests/package.json index acdd4adcb9e..778f5c56cf0 100644 --- a/extensions/vscode-custom-editor-tests/package.json +++ b/extensions/vscode-custom-editor-tests/package.json @@ -9,7 +9,6 @@ "onCustomEditor:testWebviewEditor.abc" ], "main": "./out/extension", - "enableProposedApi": true, "engines": { "vscode": "^1.48.0" }, diff --git a/extensions/vscode-notebook-tests/package.json b/extensions/vscode-notebook-tests/package.json index cc0264bb1a3..3cf93067634 100644 --- a/extensions/vscode-notebook-tests/package.json +++ b/extensions/vscode-notebook-tests/package.json @@ -9,7 +9,20 @@ "*" ], "main": "./out/extension", - "enableProposedApi": true, + "enabledApiProposals": [ + "notebookCellExecutionState", + "notebookConcatTextDocument", + "notebookContentProvider", + "notebookControllerKind", + "notebookDebugOptions", + "notebookDeprecated", + "notebookEditor", + "notebookEditorDecorationType", + "notebookEditorEdit", + "notebookLiveShare", + "notebookMessaging", + "notebookMime" + ], "engines": { "vscode": "^1.25.0" }, diff --git a/extensions/vscode-notebook-tests/tsconfig.json b/extensions/vscode-notebook-tests/tsconfig.json index 980445e09b2..c9c35c63299 100644 --- a/extensions/vscode-notebook-tests/tsconfig.json +++ b/extensions/vscode-notebook-tests/tsconfig.json @@ -9,6 +9,6 @@ "include": [ "src/**/*", "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.d.ts" + "../../src/vscode-dts/vscode.proposed.notebook*.d.ts" ] } diff --git a/extensions/vscode-test-resolver/package.json b/extensions/vscode-test-resolver/package.json index 99a836fd6c2..28cd1f1faef 100644 --- a/extensions/vscode-test-resolver/package.json +++ b/extensions/vscode-test-resolver/package.json @@ -5,6 +5,9 @@ "publisher": "vscode", "license": "MIT", "enableProposedApi": true, + "enabledApiProposals": [ + "resolvers" + ], "private": true, "engines": { "vscode": "^1.25.0" diff --git a/extensions/vscode-test-resolver/tsconfig.json b/extensions/vscode-test-resolver/tsconfig.json index 980445e09b2..f9a183ef9e1 100644 --- a/extensions/vscode-test-resolver/tsconfig.json +++ b/extensions/vscode-test-resolver/tsconfig.json @@ -9,6 +9,6 @@ "include": [ "src/**/*", "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.d.ts" + "../../src/vscode-dts/vscode.proposed.resolvers.d.ts" ] } diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 59cf0bcd5b8..a16a7bfc4b0 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -24,10 +24,10 @@ fast-plist@0.1.2: resolved "https://registry.yarnpkg.com/fast-plist/-/fast-plist-0.1.2.tgz#a45aff345196006d406ca6cdcd05f69051ef35b8" integrity sha1-pFr/NFGWAG1AbKbNzQX2kFHvNbg= -typescript@^4.5.1-rc: - version "4.5.1-rc" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.1-rc.tgz#02155eaa0579d11babb2a55dcbd796948a994bb3" - integrity sha512-tQBWW1DCFqweyhSzsAUSFgssJ3uuRBh0zyOkRV4CaFF2rMBkGU0I+MqPMch0qhGCUGXjOW7FgMrbQLS6PE3Z6Q== +typescript@4.5: + version "4.5.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998" + integrity sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw== vscode-grammar-updater@^1.0.3: version "1.0.3" diff --git a/package.json b/package.json index 368265d0742..0cd6378bac8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.63.0", - "distro": "cc8976e5470edb06e4b3abd29841ffe7bf5042d2", + "distro": "f5a2c7f01629f6d3c0dc65e3fbb3818033206bf2", "author": { "name": "Microsoft Corporation" }, @@ -59,31 +59,31 @@ }, "dependencies": { "@microsoft/applicationinsights-web": "^2.6.4", - "@parcel/watcher": "2.0.1", + "@parcel/watcher": "2.0.2", "@vscode/sqlite3": "4.0.12", + "@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", "native-is-elevated": "0.4.3", "native-keymap": "3.0.1", "native-watchdog": "1.3.0", - "node-pty": "0.11.0-beta7", + "node-pty": "0.11.0-beta11", "spdlog": "^0.13.0", - "sudo-prompt": "9.2.1", "tas-client-umd": "0.1.4", "v8-inspect-profiler": "^0.0.22", "vscode-nsfw": "2.1.8", - "vscode-oniguruma": "1.5.1", + "vscode-oniguruma": "1.6.1", "vscode-proxy-agent": "^0.11.0", "vscode-regexpp": "^3.1.0", "vscode-ripgrep": "^1.12.1", - "vscode-textmate": "5.4.1", + "vscode-textmate": "5.5.0", "xterm": "4.16.0-beta.2", "xterm-addon-search": "0.9.0-beta.6", "xterm-addon-serialize": "0.7.0-beta.3", @@ -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", @@ -197,7 +197,7 @@ "style-loader": "^1.0.0", "ts-loader": "^9.2.3", "tsec": "0.1.4", - "typescript": "^4.6.0-dev.20211108", + "typescript": "^4.6.0-dev.20211115", "typescript-formatter": "7.1.0", "underscore": "^1.12.1", "util": "^0.12.4", @@ -220,7 +220,7 @@ "url": "https://github.com/microsoft/vscode/issues" }, "optionalDependencies": { - "vscode-windows-registry": "1.0.3", + "vscode-windows-registry": "1.0.4", "windows-foreground-love": "0.4.0", "windows-mutex": "0.4.1", "windows-process-tree": "^0.3.2" diff --git a/remote/package.json b/remote/package.json index 501827c3f79..27a382951c2 100644 --- a/remote/package.json +++ b/remote/package.json @@ -4,26 +4,26 @@ "private": true, "dependencies": { "@microsoft/applicationinsights-web": "^2.6.4", - "@parcel/watcher": "2.0.1", + "@parcel/watcher": "2.0.2", "@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", - "node-pty": "0.11.0-beta7", + "node-pty": "0.11.0-beta11", "spdlog": "^0.13.0", "tas-client-umd": "0.1.4", "vscode-nsfw": "2.1.8", - "vscode-oniguruma": "1.5.1", + "vscode-oniguruma": "1.6.1", "vscode-proxy-agent": "^0.11.0", "vscode-regexpp": "^3.1.0", "vscode-ripgrep": "^1.12.1", - "vscode-textmate": "5.4.1", + "vscode-textmate": "5.5.0", "xterm": "4.16.0-beta.2", "xterm-addon-search": "0.9.0-beta.6", "xterm-addon-serialize": "0.7.0-beta.3", @@ -34,7 +34,7 @@ "yazl": "^2.4.3" }, "optionalDependencies": { - "vscode-windows-registry": "1.0.3", + "vscode-windows-registry": "1.0.4", "windows-process-tree": "^0.3.2" } } diff --git a/remote/web/package.json b/remote/web/package.json index f69e0798f01..e44ba1550a4 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -5,11 +5,11 @@ "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.5.1", - "vscode-textmate": "5.4.1", + "vscode-oniguruma": "1.6.1", + "vscode-textmate": "5.5.0", "xterm": "4.16.0-beta.2", "xterm-addon-search": "0.9.0-beta.6", "xterm-addon-unicode11": "0.4.0-beta.1", diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 4bf3abe60b5..4144af4392b 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -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" @@ -103,15 +103,15 @@ tas-client-umd@0.1.4: resolved "https://registry.yarnpkg.com/tas-client-umd/-/tas-client-umd-0.1.4.tgz#49db4130dd63a8342fabf77185a740fc6a7bea80" integrity sha512-1hFqJeLD3ryNikniIaO7TItlXhS5vx7bJ+wbPDf8o+IifgwwOWK2ARisdEM9SnJd0ccfcwNPG6Po+RiKn5L2hg== -vscode-oniguruma@1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.5.1.tgz#9ca10cd3ada128bd6380344ea28844243d11f695" - integrity sha512-JrBZH8DCC262TEYcYdeyZusiETu0Vli0xFgdRwNJjDcObcRjbmJP+IFcA3ScBwIXwgFHYKbAgfxtM/Cl+3Spjw== +vscode-oniguruma@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.6.1.tgz#2bf4dfcfe3dd2e56eb549a3068c8ee39e6c30ce5" + integrity sha512-vc4WhSIaVpgJ0jJIejjYxPvURJavX6QG41vu0mGhqywMkQqulezEqEQ3cO3gc8GvcOpX6ycmKGqRoROEMBNXTQ== -vscode-textmate@5.4.1: - version "5.4.1" - resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.4.1.tgz#09d566724fc76b60b3ad9791eebf1f0b50f29e5a" - integrity sha512-4CvPHmfuZQaXrcCpathdh6jo7myuR+MU8BvscgQADuponpbqfmu2rwTOtCXhGwwEgStvJF8V4s9FwMKRVLNmKQ== +vscode-textmate@5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.5.0.tgz#d83776562c07d1e3181c2c7f1b3d5f20afcab483" + integrity sha512-jToQkPGMNKn0eyKyitYeINJF0NoD240aYyKPIWJv5W2jfPt++jIRg0OSergubtGhbw6SoefkvBYEpX7TsfoSUQ== xterm-addon-search@0.9.0-beta.6: version "0.9.0-beta.6" diff --git a/remote/yarn.lock b/remote/yarn.lock index 6eb9ea16e1b..5b1c1bd4b32 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -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.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.1.tgz#ec4bb6c43d9588a1ffd3d2abe6df5b501463c62d" - integrity sha512-XegFF4L8sFn1RzU5KKOZxXUuzgOSwd6+X2ez3Cy6MVhYMbiLZ1moceMTqDhuT3N8DNbdumK3zP1wojsIsnX40w== +"@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== 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" @@ -394,10 +394,10 @@ node-gyp-build@^4.3.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== -node-pty@0.11.0-beta7: - version "0.11.0-beta7" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.11.0-beta7.tgz#aed0888b5032d96c54d8473455e6adfae3bbebbe" - integrity sha512-uApPGLglZRiHQcUMWakbZOrBo8HVWvhzIqNnrWvBGJOvc6m/S5lCdbbg93BURyJqHFmBS0GV+4hwiMNDuGRbSA== +node-pty@0.11.0-beta11: + version "0.11.0-beta11" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.11.0-beta11.tgz#10843516868129c26a97253903c46fe0e4520eb0" + integrity sha512-Gw58duqHle4k/BunssCE1CUKKWipRQZTUFhaTegkKC19fw3IXsvillblLUuD2bQL42+3mQCAFSgTDo+OsJzYCQ== dependencies: nan "^2.14.0" @@ -479,10 +479,10 @@ vscode-nsfw@2.1.8: dependencies: node-addon-api "^4.2.0" -vscode-oniguruma@1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.5.1.tgz#9ca10cd3ada128bd6380344ea28844243d11f695" - integrity sha512-JrBZH8DCC262TEYcYdeyZusiETu0Vli0xFgdRwNJjDcObcRjbmJP+IFcA3ScBwIXwgFHYKbAgfxtM/Cl+3Spjw== +vscode-oniguruma@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.6.1.tgz#2bf4dfcfe3dd2e56eb549a3068c8ee39e6c30ce5" + integrity sha512-vc4WhSIaVpgJ0jJIejjYxPvURJavX6QG41vu0mGhqywMkQqulezEqEQ3cO3gc8GvcOpX6ycmKGqRoROEMBNXTQ== vscode-proxy-agent@^0.11.0: version "0.11.0" @@ -512,10 +512,10 @@ vscode-ripgrep@^1.12.1: https-proxy-agent "^4.0.0" proxy-from-env "^1.1.0" -vscode-textmate@5.4.1: - version "5.4.1" - resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.4.1.tgz#09d566724fc76b60b3ad9791eebf1f0b50f29e5a" - integrity sha512-4CvPHmfuZQaXrcCpathdh6jo7myuR+MU8BvscgQADuponpbqfmu2rwTOtCXhGwwEgStvJF8V4s9FwMKRVLNmKQ== +vscode-textmate@5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.5.0.tgz#d83776562c07d1e3181c2c7f1b3d5f20afcab483" + integrity sha512-jToQkPGMNKn0eyKyitYeINJF0NoD240aYyKPIWJv5W2jfPt++jIRg0OSergubtGhbw6SoefkvBYEpX7TsfoSUQ== vscode-windows-ca-certs@^0.3.0: version "0.3.0" @@ -524,10 +524,10 @@ vscode-windows-ca-certs@^0.3.0: dependencies: node-addon-api "^3.0.2" -vscode-windows-registry@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/vscode-windows-registry/-/vscode-windows-registry-1.0.3.tgz#377e9a8bf75c0acac81a188282a4f16f748ecd47" - integrity sha512-IXCwNAm+H5yPCn6JBz89T9AAMgy5xEN2LxbxrvHPlErmyQqCYtpCCjvisfgT2dCuaJ2T9FfiqIeIrOpDm2Jc4Q== +vscode-windows-registry@1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/vscode-windows-registry/-/vscode-windows-registry-1.0.4.tgz#9e565a497c84b6b82d200f88930baeff12912079" + integrity sha512-vjYaMzEygZrb8bN6I33XTajpF/gtDOk5CtQPPSaxanXg2kkrerEM9qovY6t6FtBGl3oLq6YHytYdYw4IpXgJdA== windows-process-tree@^0.3.2: version "0.3.2" diff --git a/resources/server/bin-dev/code-web.js b/resources/server/bin-dev/code-web.js index d5276fb41d0..0fdb7c76d2c 100644 --- a/resources/server/bin-dev/code-web.js +++ b/resources/server/bin-dev/code-web.js @@ -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'); diff --git a/resources/web/code-web.js b/resources/web/code-web.js index 0625a848805..e2e23f9edec 100644 --- a/resources/web/code-web.js +++ b/resources/web/code-web.js @@ -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: [ diff --git a/scripts/test.bat b/scripts/test.bat index 7232c26193c..d45505db8a7 100644 --- a/scripts/test.bat +++ b/scripts/test.bat @@ -17,7 +17,7 @@ if %errorlevel% neq 0 node .\node_modules\gulp\bin\gulp.js electron :: Run tests set ELECTRON_ENABLE_LOGGING=1 -%CODE% .\test\unit\electron\index.js %* +%CODE% .\test\unit\electron\index.js --crash-reporter-directory=%~dp0\..\.build\crashes %* popd diff --git a/scripts/test.sh b/scripts/test.sh index 10ffb97c71f..14a72e09cc3 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -21,6 +21,8 @@ else CODE=".build/electron/$NAME" fi +VSCODECRASHDIR=$ROOT/.build/crashes + # Node modules test -d node_modules || yarn @@ -32,10 +34,10 @@ if [[ "$OSTYPE" == "darwin"* ]]; then cd $ROOT ; ulimit -n 4096 ; \ ELECTRON_ENABLE_LOGGING=1 \ "$CODE" \ - test/unit/electron/index.js "$@" + test/unit/electron/index.js --crash-reporter-directory=$VSCODECRASHDIR "$@" else cd $ROOT ; \ ELECTRON_ENABLE_LOGGING=1 \ "$CODE" \ - test/unit/electron/index.js $LINUX_EXTRA_ARGS "$@" + test/unit/electron/index.js --crash-reporter-directory=$VSCODECRASHDIR $LINUX_EXTRA_ARGS "$@" fi diff --git a/src/main.js b/src/main.js index ad838aa245e..4f3e6ef6930 100644 --- a/src/main.js +++ b/src/main.js @@ -25,8 +25,6 @@ const { getUserDataPath } = require('./vs/platform/environment/node/userDataPath const product = require('../product.json'); const { app, protocol, crashReporter } = require('electron'); -// Disable render process reuse, we still have -// non-context aware native modules in the renderer. app.allowRendererProcessReuse = false; // Enable portable support @@ -175,7 +173,10 @@ function configureCommandlineSwitchesSync(cliArgs) { 'enable-proposed-api', // Log level to use. Default is 'info'. Allowed values are 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'. - 'log-level' + 'log-level', + + // Enables render process reuse. Default value is 'false'. See https://github.com/electron/electron/issues/18397 + 'enable-render-process-reuse' ]; // Read argv config @@ -220,6 +221,12 @@ function configureCommandlineSwitchesSync(cliArgs) { process.argv.push('--log', argvValue); } break; + + case 'enable-render-process-reuse': + if (argvValue === true) { + app.allowRendererProcessReuse = true; + } + break; } } }); diff --git a/src/tsconfig.json b/src/tsconfig.json index 02e85647832..eaaa3fb52b8 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -26,7 +26,6 @@ "./typings", "./vs", "vscode-dts/vscode.proposed.*.d.ts", - "vscode-dts/vscode.proposed.d.ts", "vscode-dts/vscode.d.ts" ] } diff --git a/src/tsconfig.vscode-proposed-dts.json b/src/tsconfig.vscode-proposed-dts.json index 802e37e567e..dd12f3bca28 100644 --- a/src/tsconfig.vscode-proposed-dts.json +++ b/src/tsconfig.vscode-proposed-dts.json @@ -2,6 +2,6 @@ "extends": "./tsconfig.vscode-dts.json", "include": [ "vscode-dts/vscode.d.ts", - "vscode-dts/vscode.proposed.d.ts", + "vscode-dts/vscode.proposed.*.d.ts", ] } diff --git a/src/tsec.exemptions.json b/src/tsec.exemptions.json index fbb798ca0e3..7e7edeefd22 100644 --- a/src/tsec.exemptions.json +++ b/src/tsec.exemptions.json @@ -32,6 +32,7 @@ "vs/platform/sharedProcess/electron-browser/sharedProcessWorkerService.ts" ], "ban-domparser-parsefromstring": [ + "vs/base/browser/markdownRenderer.ts", "vs/base/test/browser/markdownRenderer.test.ts" ] } diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index de0524c037e..cff9352a148 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -908,7 +908,7 @@ export const EventHelper = { export interface IFocusTracker extends Disposable { onDidFocus: Event; onDidBlur: Event; - refreshState?(): void; + refreshState(): void; } export function saveParentsScrollTop(node: Element): number[] { @@ -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() { diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index 70995984352..5d885f37ea3 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -71,20 +71,23 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende const _href = function (href: string, isDomUri: boolean): string { const data = markdown.uris && markdown.uris[href]; - if (!data) { - return href; // no uri exists - } let uri = URI.revive(data); if (isDomUri) { if (href.startsWith(Schemas.data + ':')) { return href; } + if (!uri) { + uri = URI.parse(href); + } // this URI will end up as "src"-attribute of a dom node // and because of that special rewriting needs to be done // so that the URI uses a protocol that's understood by // browsers (like http or https) return FileAccess.asBrowserUri(uri).toString(true); } + if (!uri) { + return href; + } if (URI.parse(href).toString() === uri.toString()) { return href; // no transformation performed } @@ -100,19 +103,12 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende const withInnerHTML = new Promise(c => signalInnerHTML = c); const renderer = new marked.Renderer(); + renderer.image = (href: string, title: string, text: string) => { let dimensions: string[] = []; let attributes: string[] = []; if (href) { ({ href, dimensions } = parseHrefAndDimensions(href)); - href = _href(href, true); - try { - const hrefAsUri = URI.parse(href); - if (options.baseUrl && hrefAsUri.scheme === Schemas.file) { // absolute or relative local path, or file: uri - href = resolvePath(options.baseUrl, href).toString(); - } - } catch (err) { } - attributes.push(`src="${href}"`); } if (text) { @@ -250,7 +246,26 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende renderedMarkdown = elements.map(e => typeof e === 'string' ? e : e.outerHTML).join(''); } - element.innerHTML = sanitizeRenderedMarkdown(markdown, renderedMarkdown) as unknown as string; + const htmlParser = new DOMParser(); + const markdownHtmlDoc = htmlParser.parseFromString(sanitizeRenderedMarkdown(markdown, renderedMarkdown) as unknown as string, 'text/html'); + + markdownHtmlDoc.body.querySelectorAll('img') + .forEach(img => { + if (img.src) { + let href = _href(img.src, true); + + try { + const hrefAsUri = URI.parse(href); + if (options.baseUrl && hrefAsUri.scheme === Schemas.file) { // absolute or relative local path, or file: uri + href = resolvePath(options.baseUrl, href).toString(); + } + } catch (err) { } + + img.src = href; + } + }); + + element.innerHTML = sanitizeRenderedMarkdown(markdown, markdownHtmlDoc.body.innerHTML) as unknown as string; // signal that async code blocks can be now be inserted signalInnerHTML!(); diff --git a/src/vs/base/browser/ui/checkbox/checkbox.css b/src/vs/base/browser/ui/checkbox/checkbox.css index c754adc1518..706c9585e40 100644 --- a/src/vs/base/browser/ui/checkbox/checkbox.css +++ b/src/vs/base/browser/ui/checkbox/checkbox.css @@ -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 { diff --git a/src/vs/base/browser/ui/checkbox/checkbox.ts b/src/vs/base/browser/ui/checkbox/checkbox.ts index b66ce0497db..27b4c5875fa 100644 --- a/src/vs/base/browser/ui/checkbox/checkbox.ts +++ b/src/vs/base/browser/ui/checkbox/checkbox.ts @@ -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() : ''; } } diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts index b80ffaecb29..113822bdffd 100644 --- a/src/vs/base/browser/ui/grid/gridview.ts +++ b/src/vs/base/browser/ui/grid/gridview.ts @@ -10,7 +10,7 @@ import { equals as arrayEquals, tail2 as tail } from 'vs/base/common/arrays'; import { Color } from 'vs/base/common/color'; import { Emitter, Event, Relay } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { clamp } from 'vs/base/common/numbers'; +import { rot } from 'vs/base/common/numbers'; import { isUndefined } from 'vs/base/common/types'; import 'vs/css!./gridview'; @@ -235,6 +235,14 @@ function fromAbsoluteBoundarySashes(sashes: IBoundarySashes, orientation: Orient } } +function validateIndex(index: number, numChildren: number): number { + if (Math.abs(index) > numChildren) { + throw new Error('Invalid index'); + } + + return rot(index, numChildren + 1); +} + class BranchNode implements ISplitView, IDisposable { readonly element: HTMLElement; @@ -474,9 +482,7 @@ class BranchNode implements ISplitView, IDisposable { } addChild(node: Node, size: number | Sizing, index: number, skipLayout?: boolean): void { - if (index < 0 || index > this.children.length) { - throw new Error('Invalid index'); - } + index = validateIndex(index, this.children.length); this.splitview.addView(node, size, index, skipLayout); this._addChild(node, index); @@ -511,9 +517,7 @@ class BranchNode implements ISplitView, IDisposable { } removeChild(index: number, sizing?: Sizing): void { - if (index < 0 || index >= this.children.length) { - throw new Error('Invalid index'); - } + index = validateIndex(index, this.children.length); this.splitview.removeView(index, sizing); this._removeChild(index); @@ -543,16 +547,13 @@ class BranchNode implements ISplitView, IDisposable { } moveChild(from: number, to: number): void { + from = validateIndex(from, this.children.length); + to = validateIndex(to, this.children.length); + if (from === to) { return; } - if (from < 0 || from >= this.children.length) { - throw new Error('Invalid from index'); - } - - to = clamp(to, 0, this.children.length); - if (from < to) { to--; } @@ -566,16 +567,13 @@ class BranchNode implements ISplitView, IDisposable { } swapChildren(from: number, to: number): void { + from = validateIndex(from, this.children.length); + to = validateIndex(to, this.children.length); + if (from === to) { return; } - if (from < 0 || from >= this.children.length) { - throw new Error('Invalid from index'); - } - - to = clamp(to, 0, this.children.length); - this.splitview.swapViews(from, to); // swap boundary sashes @@ -589,9 +587,7 @@ class BranchNode implements ISplitView, IDisposable { } resizeChild(index: number, size: number): void { - if (index < 0 || index >= this.children.length) { - throw new Error('Invalid index'); - } + index = validateIndex(index, this.children.length); this.splitview.resizeView(index, size); } @@ -609,25 +605,19 @@ class BranchNode implements ISplitView, IDisposable { } getChildSize(index: number): number { - if (index < 0 || index >= this.children.length) { - throw new Error('Invalid index'); - } + index = validateIndex(index, this.children.length); return this.splitview.getViewSize(index); } isChildVisible(index: number): boolean { - if (index < 0 || index >= this.children.length) { - throw new Error('Invalid index'); - } + index = validateIndex(index, this.children.length); return this.splitview.isViewVisible(index); } setChildVisible(index: number, visible: boolean): void { - if (index < 0 || index >= this.children.length) { - throw new Error('Invalid index'); - } + index = validateIndex(index, this.children.length); if (this.splitview.isViewVisible(index) === visible) { return; @@ -637,9 +627,7 @@ class BranchNode implements ISplitView, IDisposable { } getChildCachedVisibleSize(index: number): number | undefined { - if (index < 0 || index >= this.children.length) { - throw new Error('Invalid index'); - } + index = validateIndex(index, this.children.length); return this.splitview.getViewCachedVisibleSize(index); } diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index a6ba042ad66..f04858f1545 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -363,11 +363,8 @@ export class InputBox extends Widget { } public set paddingRight(paddingRight: number) { - if (this.options.flexibleHeight && this.options.flexibleWidth) { - this.input.style.width = `calc(100% - ${paddingRight}px)`; - } else { - this.input.style.paddingRight = paddingRight + 'px'; - } + // Set width to avoid hint text overlapping buttons + this.input.style.width = `calc(100% - ${paddingRight}px)`; if (this.mirror) { this.mirror.style.paddingRight = paddingRight + 'px'; diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index dd3ec826f13..6469e3f3b45 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -59,6 +59,10 @@ export interface IMenuStyles { selectionBackgroundColor?: Color; selectionBorderColor?: Color; separatorColor?: Color; + scrollbarShadow?: Color; + scrollbarSliderBackground?: Color; + scrollbarSliderHoverBackground?: Color; + scrollbarSliderActiveBackground?: Color; } interface ISubMenuData { @@ -99,8 +103,6 @@ export class Menu extends ActionBar { this.menuDisposables = this._register(new DisposableStore()); - this.initializeStyleSheet(container); - this._register(Gesture.addTarget(menuElement)); addDisposableListener(menuElement, EventType.KEY_DOWN, (e) => { @@ -262,23 +264,25 @@ export class Menu extends ActionBar { }); } - private initializeStyleSheet(container: HTMLElement): void { - if (isInShadowDOM(container)) { - this.styleSheet = createStyleSheet(container); - this.styleSheet.textContent = MENU_WIDGET_CSS; - } else { - if (!Menu.globalStyleSheet) { - Menu.globalStyleSheet = createStyleSheet(); - Menu.globalStyleSheet.textContent = MENU_WIDGET_CSS; + private initializeOrUpdateStyleSheet(container: HTMLElement, style: IMenuStyles): void { + if (!this.styleSheet) { + if (isInShadowDOM(container)) { + this.styleSheet = createStyleSheet(container); + } else { + if (!Menu.globalStyleSheet) { + Menu.globalStyleSheet = createStyleSheet(); + } + this.styleSheet = Menu.globalStyleSheet; } - - this.styleSheet = Menu.globalStyleSheet; } + this.styleSheet.textContent = getMenuWidgetCSS(style); } style(style: IMenuStyles): void { const container = this.getContainer(); + this.initializeOrUpdateStyleSheet(container, style); + const fgColor = style.foregroundColor ? `${style.foregroundColor}` : ''; const bgColor = style.backgroundColor ? `${style.backgroundColor}` : ''; const border = style.borderColor ? `1px solid ${style.borderColor}` : ''; @@ -1016,7 +1020,8 @@ export function cleanMnemonic(label: string): string { return label.replace(regex, mnemonicInText ? '$2$3' : '').trim(); } -let MENU_WIDGET_CSS: string = /* css */` +function getMenuWidgetCSS(style: IMenuStyles): string { + let result = /* css */` .monaco-menu { font-size: 13px; @@ -1339,7 +1344,6 @@ ${formatRule(Codicon.menuSubmenu)} left: 3px; height: 3px; width: 100%; - box-shadow: #DDD 0 6px 6px -6px inset; } .monaco-scrollable-element > .shadow.left { display: block; @@ -1347,7 +1351,6 @@ ${formatRule(Codicon.menuSubmenu)} left: 0; height: 100%; width: 3px; - box-shadow: #DDD 6px 0 6px -6px inset; } .monaco-scrollable-element > .shadow.top-left-corner { display: block; @@ -1356,60 +1359,52 @@ ${formatRule(Codicon.menuSubmenu)} height: 3px; width: 3px; } -.monaco-scrollable-element > .shadow.top.left { - box-shadow: #DDD 6px 6px 6px -6px inset; -} - -/* ---------- Default Style ---------- */ - -:host-context(.vs) .monaco-scrollable-element > .scrollbar > .slider { - background: rgba(100, 100, 100, .4); -} -:host-context(.vs-dark) .monaco-scrollable-element > .scrollbar > .slider { - background: rgba(121, 121, 121, .4); -} -:host-context(.hc-black) .monaco-scrollable-element > .scrollbar > .slider { - background: rgba(111, 195, 223, .6); -} - -.monaco-scrollable-element > .scrollbar > .slider:hover { - background: rgba(100, 100, 100, .7); -} -:host-context(.hc-black) .monaco-scrollable-element > .scrollbar > .slider:hover { - background: rgba(111, 195, 223, .8); -} - -.monaco-scrollable-element > .scrollbar > .slider.active { - background: rgba(0, 0, 0, .6); -} -:host-context(.vs-dark) .monaco-scrollable-element > .scrollbar > .slider.active { - background: rgba(191, 191, 191, .4); -} -:host-context(.hc-black) .monaco-scrollable-element > .scrollbar > .slider.active { - background: rgba(111, 195, 223, 1); -} - -:host-context(.vs-dark) .monaco-scrollable-element .shadow.top { - box-shadow: none; -} - -:host-context(.vs-dark) .monaco-scrollable-element .shadow.left { - box-shadow: #000 6px 0 6px -6px inset; -} - -:host-context(.vs-dark) .monaco-scrollable-element .shadow.top.left { - box-shadow: #000 6px 6px 6px -6px inset; -} - -:host-context(.hc-black) .monaco-scrollable-element .shadow.top { - box-shadow: none; -} - -:host-context(.hc-black) .monaco-scrollable-element .shadow.left { - box-shadow: none; -} - -:host-context(.hc-black) .monaco-scrollable-element .shadow.top.left { - box-shadow: none; -} `; + + // Scrollbars + const scrollbarShadowColor = style.scrollbarShadow; + if (scrollbarShadowColor) { + result += ` + .monaco-scrollable-element > .shadow.top { + box-shadow: ${scrollbarShadowColor} 0 6px 6px -6px inset; + } + + .monaco-scrollable-element > .shadow.left { + box-shadow: ${scrollbarShadowColor} 6px 0 6px -6px inset; + } + + .monaco-scrollable-element > .shadow.top.left { + box-shadow: ${scrollbarShadowColor} 6px 6px 6px -6px inset; + } + `; + } + + const scrollbarSliderBackgroundColor = style.scrollbarSliderBackground; + if (scrollbarSliderBackgroundColor) { + result += ` + .monaco-scrollable-element > .scrollbar > .slider { + background: ${scrollbarSliderBackgroundColor}; + } + `; + } + + const scrollbarSliderHoverBackgroundColor = style.scrollbarSliderHoverBackground; + if (scrollbarSliderHoverBackgroundColor) { + result += ` + .monaco-scrollable-element > .scrollbar > .slider:hover { + background: ${scrollbarSliderHoverBackgroundColor}; + } + `; + } + + const scrollbarSliderActiveBackgroundColor = style.scrollbarSliderActiveBackground; + if (scrollbarSliderActiveBackgroundColor) { + result += ` + .monaco-scrollable-element > .scrollbar > .slider.active { + background: ${scrollbarSliderActiveBackgroundColor}; + } + `; + } + + return result; +} diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 0972e6f8bf1..f2f264f747c 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -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(obj: unknown): obj is Promise { return !!obj && typeof (obj as unknown as Promise).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); } }; }; diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index 100ec15f577..26792a1bd03 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -598,9 +598,9 @@ export interface CSSIcon { export namespace CSSIcon { export const iconNameSegment = '[A-Za-z0-9]+'; - export const iconNameExpression = '[A-Za-z0-9\\-]+'; + export const iconNameExpression = '[A-Za-z0-9-]+'; export const iconModifierExpression = '~[A-Za-z]+'; - export const iconNameCharacter = '[A-Za-z0-9\\-~]'; + export const iconNameCharacter = '[A-Za-z0-9~-]'; const cssIconIdRegex = new RegExp(`^(${iconNameExpression})(${iconModifierExpression})?$`); diff --git a/src/vs/base/common/keybindingLabels.ts b/src/vs/base/common/keybindingLabels.ts index c4857834c88..62e905a4bb4 100644 --- a/src/vs/base/common/keybindingLabels.ts +++ b/src/vs/base/common/keybindingLabels.ts @@ -54,8 +54,7 @@ export class ModifierLabelProvider { */ export const UILabelProvider = new ModifierLabelProvider( { - // allow-any-unicode-next-line - ctrlKey: '⌃', + ctrlKey: '\u2303', shiftKey: '⇧', altKey: '⌥', metaKey: '⌘', @@ -84,7 +83,7 @@ export const AriaLabelProvider = new ModifierLabelProvider( { ctrlKey: nls.localize({ key: 'ctrlKey.long', comment: ['This is the long form for the Control key on the keyboard'] }, "Control"), shiftKey: nls.localize({ key: 'shiftKey.long', comment: ['This is the long form for the Shift key on the keyboard'] }, "Shift"), - altKey: nls.localize({ key: 'altKey.long', comment: ['This is the long form for the Alt key on the keyboard'] }, "Alt"), + altKey: nls.localize({ key: 'optKey.long', comment: ['This is the long form for the Alt/Option key on the keyboard'] }, "Option"), metaKey: nls.localize({ key: 'cmdKey.long', comment: ['This is the long form for the Command key on the keyboard'] }, "Command"), separator: '+', }, diff --git a/src/vs/base/common/marked/marked.js b/src/vs/base/common/marked/marked.js index b7a725a1bca..3a3f975c922 100644 --- a/src/vs/base/common/marked/marked.js +++ b/src/vs/base/common/marked/marked.js @@ -2980,14 +2980,14 @@ // ESM-uncomment-begin // })(); -// export var marked = __marked_exports; -// export var Parser = __marked_exports.Parser; -// export var parser = __marked_exports.parser; -// export var Renderer = __marked_exports.Renderer; -// export var TextRenderer = __marked_exports.TextRenderer; -// export var Lexer = __marked_exports.Lexer; -// export var lexer = __marked_exports.lexer; -// export var Tokenizer = __marked_exports.Tokenizer; -// export var Slugger = __marked_exports.Slugger; -// export var parse = __marked_exports.parse; +// export var marked = (__marked_exports || exports); +// export var Parser = (__marked_exports || exports).Parser; +// export var parser = (__marked_exports || exports).parser; +// export var Renderer = (__marked_exports || exports).Renderer; +// export var TextRenderer = (__marked_exports || exports).TextRenderer; +// export var Lexer = (__marked_exports || exports).Lexer; +// export var lexer = (__marked_exports || exports).lexer; +// export var Tokenizer = (__marked_exports || exports).Tokenizer; +// export var Slugger = (__marked_exports || exports).Slugger; +// export var parse = (__marked_exports || exports).parse; // ESM-uncomment-end diff --git a/src/vs/base/common/performance.js b/src/vs/base/common/performance.js index d15367be8b2..15eab308bf2 100644 --- a/src/vs/base/common/performance.js +++ b/src/vs/base/common/performance.js @@ -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) { diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index 1a571e41337..973ac98d615 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -190,10 +190,13 @@ 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 +204,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,9 +222,19 @@ export const setImmediate: ISetImmediate = (function defineSetImmediate() { id: myId, callback: callback }); - globals.postMessage({ vscodeSetImmediateId: myId }, '*'); + globals.postMessage({ vscodeScheduleAsyncWork: myId }, '*'); }; } + return (callback: () => void) => setTimeout(callback); +})(); + +export const setImmediate: ISetImmediate = (function defineSetImmediate() { + if (globals.setImmediate) { + return globals.setImmediate.bind(globals); + } + if (typeof globals.postMessage === 'function' && !globals.importScripts) { + return setTimeout0; + } if (typeof nodeProcess?.nextTick === 'function') { return nodeProcess.nextTick.bind(nodeProcess); } diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index e5a0af70bb2..ac67491f049 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -549,9 +549,9 @@ export function getCharContainingOffset(str: string, offset: number): [number, n } /** - * Generated using https://github.com/alexdima/unicode-utils/blob/master/generate-rtl-test.js + * Generated using https://github.com/alexdima/unicode-utils/blob/main/rtl-test.js */ -const CONTAINS_RTL = /(?:[\u05BE\u05C0\u05C3\u05C6\u05D0-\u05F4\u0608\u060B\u060D\u061B-\u064A\u066D-\u066F\u0671-\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u0710\u0712-\u072F\u074D-\u07A5\u07B1-\u07EA\u07F4\u07F5\u07FA-\u0815\u081A\u0824\u0828\u0830-\u0858\u085E-\u08BD\u200F\uFB1D\uFB1F-\uFB28\uFB2A-\uFD3D\uFD50-\uFDFC\uFE70-\uFEFC]|\uD802[\uDC00-\uDD1B\uDD20-\uDE00\uDE10-\uDE33\uDE40-\uDEE4\uDEEB-\uDF35\uDF40-\uDFFF]|\uD803[\uDC00-\uDCFF]|\uD83A[\uDC00-\uDCCF\uDD00-\uDD43\uDD50-\uDFFF]|\uD83B[\uDC00-\uDEBB])/; +const CONTAINS_RTL = /(?:[\u05BE\u05C0\u05C3\u05C6\u05D0-\u05F4\u0608\u060B\u060D\u061B-\u064A\u066D-\u066F\u0671-\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u0710\u0712-\u072F\u074D-\u07A5\u07B1-\u07EA\u07F4\u07F5\u07FA\u07FE-\u0815\u081A\u0824\u0828\u0830-\u0858\u085E-\u088E\u08A0-\u08C9\u200F\uFB1D\uFB1F-\uFB28\uFB2A-\uFD3D\uFD50-\uFDC7\uFDF0-\uFDFC\uFE70-\uFEFC]|\uD802[\uDC00-\uDD1B\uDD20-\uDE00\uDE10-\uDE35\uDE40-\uDEE4\uDEEB-\uDF35\uDF40-\uDFFF]|\uD803[\uDC00-\uDD23\uDE80-\uDEA9\uDEAD-\uDF45\uDF51-\uDF81\uDF86-\uDFF6]|\uD83A[\uDC00-\uDCCF\uDD00-\uDD43\uDD4B-\uDFFF]|\uD83B[\uDC00-\uDEBB])/; /** * Returns true if `str` contains any Unicode character that is classified as "R" or "AL". @@ -561,9 +561,9 @@ export function containsRTL(str: string): boolean { } /** - * Generated using https://github.com/alexdima/unicode-utils/blob/master/generate-emoji-test.js + * Generated using https://github.com/alexdima/unicode-utils/blob/main/emoji-test.js */ -const CONTAINS_EMOJI = /(?:[\u231A\u231B\u23F0\u23F3\u2600-\u27BF\u2B50\u2B55]|\uD83C[\uDDE6-\uDDFF\uDF00-\uDFFF]|\uD83D[\uDC00-\uDE4F\uDE80-\uDEFC\uDFE0-\uDFEB]|\uD83E[\uDD00-\uDDFF\uDE70-\uDED6])/; +const CONTAINS_EMOJI = /(?:[\u231A\u231B\u23F0\u23F3\u2600-\u27BF\u2B50\u2B55]|\uD83C[\uDDE6-\uDDFF\uDF00-\uDFFF]|\uD83D[\uDC00-\uDE4F\uDE80-\uDEFC\uDFE0-\uDFF0]|\uD83E[\uDD00-\uDDFF\uDE70-\uDEF6])/; export function containsEmoji(str: string): boolean { return CONTAINS_EMOJI.test(str); @@ -642,15 +642,15 @@ export function isFullWidthCharacter(charCode: number): boolean { /** * A fast function (therefore imprecise) to check if code points are emojis. - * Generated using https://github.com/alexdima/unicode-utils/blob/master/generate-emoji-test.js + * Generated using https://github.com/alexdima/unicode-utils/blob/main/emoji-test.js */ export function isEmojiImprecise(x: number): boolean { return ( (x >= 0x1F1E6 && x <= 0x1F1FF) || (x === 8986) || (x === 8987) || (x === 9200) || (x === 9203) || (x >= 9728 && x <= 10175) || (x === 11088) || (x === 11093) || (x >= 127744 && x <= 128591) || (x >= 128640 && x <= 128764) - || (x >= 128992 && x <= 129003) || (x >= 129280 && x <= 129535) - || (x >= 129648 && x <= 129750) + || (x >= 128992 && x <= 129008) || (x >= 129280 && x <= 129535) + || (x >= 129648 && x <= 129782) ); } @@ -948,8 +948,8 @@ class GraphemeBreakTree { } function getGraphemeBreakRawData(): number[] { - // generated using https://github.com/alexdima/unicode-utils/blob/master/generate-grapheme-break.js - return JSON.parse('[0,0,0,51592,51592,11,44424,44424,11,72251,72254,5,7150,7150,7,48008,48008,11,55176,55176,11,128420,128420,14,3276,3277,5,9979,9980,14,46216,46216,11,49800,49800,11,53384,53384,11,70726,70726,5,122915,122916,5,129320,129327,14,2558,2558,5,5906,5908,5,9762,9763,14,43360,43388,8,45320,45320,11,47112,47112,11,48904,48904,11,50696,50696,11,52488,52488,11,54280,54280,11,70082,70083,1,71350,71350,7,73111,73111,5,127892,127893,14,128726,128727,14,129473,129474,14,2027,2035,5,2901,2902,5,3784,3789,5,6754,6754,5,8418,8420,5,9877,9877,14,11088,11088,14,44008,44008,5,44872,44872,11,45768,45768,11,46664,46664,11,47560,47560,11,48456,48456,11,49352,49352,11,50248,50248,11,51144,51144,11,52040,52040,11,52936,52936,11,53832,53832,11,54728,54728,11,69811,69814,5,70459,70460,5,71096,71099,7,71998,71998,5,72874,72880,5,119149,119149,7,127374,127374,14,128335,128335,14,128482,128482,14,128765,128767,14,129399,129400,14,129680,129685,14,1476,1477,5,2377,2380,7,2759,2760,5,3137,3140,7,3458,3459,7,4153,4154,5,6432,6434,5,6978,6978,5,7675,7679,5,9723,9726,14,9823,9823,14,9919,9923,14,10035,10036,14,42736,42737,5,43596,43596,5,44200,44200,11,44648,44648,11,45096,45096,11,45544,45544,11,45992,45992,11,46440,46440,11,46888,46888,11,47336,47336,11,47784,47784,11,48232,48232,11,48680,48680,11,49128,49128,11,49576,49576,11,50024,50024,11,50472,50472,11,50920,50920,11,51368,51368,11,51816,51816,11,52264,52264,11,52712,52712,11,53160,53160,11,53608,53608,11,54056,54056,11,54504,54504,11,54952,54952,11,68108,68111,5,69933,69940,5,70197,70197,7,70498,70499,7,70845,70845,5,71229,71229,5,71727,71735,5,72154,72155,5,72344,72345,5,73023,73029,5,94095,94098,5,121403,121452,5,126981,127182,14,127538,127546,14,127990,127990,14,128391,128391,14,128445,128449,14,128500,128505,14,128752,128752,14,129160,129167,14,129356,129356,14,129432,129442,14,129648,129651,14,129751,131069,14,173,173,4,1757,1757,1,2274,2274,1,2494,2494,5,2641,2641,5,2876,2876,5,3014,3016,7,3262,3262,7,3393,3396,5,3570,3571,7,3968,3972,5,4228,4228,7,6086,6086,5,6679,6680,5,6912,6915,5,7080,7081,5,7380,7392,5,8252,8252,14,9096,9096,14,9748,9749,14,9784,9786,14,9833,9850,14,9890,9894,14,9938,9938,14,9999,9999,14,10085,10087,14,12349,12349,14,43136,43137,7,43454,43456,7,43755,43755,7,44088,44088,11,44312,44312,11,44536,44536,11,44760,44760,11,44984,44984,11,45208,45208,11,45432,45432,11,45656,45656,11,45880,45880,11,46104,46104,11,46328,46328,11,46552,46552,11,46776,46776,11,47000,47000,11,47224,47224,11,47448,47448,11,47672,47672,11,47896,47896,11,48120,48120,11,48344,48344,11,48568,48568,11,48792,48792,11,49016,49016,11,49240,49240,11,49464,49464,11,49688,49688,11,49912,49912,11,50136,50136,11,50360,50360,11,50584,50584,11,50808,50808,11,51032,51032,11,51256,51256,11,51480,51480,11,51704,51704,11,51928,51928,11,52152,52152,11,52376,52376,11,52600,52600,11,52824,52824,11,53048,53048,11,53272,53272,11,53496,53496,11,53720,53720,11,53944,53944,11,54168,54168,11,54392,54392,11,54616,54616,11,54840,54840,11,55064,55064,11,65438,65439,5,69633,69633,5,69837,69837,1,70018,70018,7,70188,70190,7,70368,70370,7,70465,70468,7,70712,70719,5,70835,70840,5,70850,70851,5,71132,71133,5,71340,71340,7,71458,71461,5,71985,71989,7,72002,72002,7,72193,72202,5,72281,72283,5,72766,72766,7,72885,72886,5,73104,73105,5,92912,92916,5,113824,113827,4,119173,119179,5,121505,121519,5,125136,125142,5,127279,127279,14,127489,127490,14,127570,127743,14,127900,127901,14,128254,128254,14,128369,128370,14,128400,128400,14,128425,128432,14,128468,128475,14,128489,128494,14,128715,128720,14,128745,128745,14,128759,128760,14,129004,129023,14,129296,129304,14,129340,129342,14,129388,129392,14,129404,129407,14,129454,129455,14,129485,129487,14,129659,129663,14,129719,129727,14,917536,917631,5,13,13,2,1160,1161,5,1564,1564,4,1807,1807,1,2085,2087,5,2363,2363,7,2402,2403,5,2507,2508,7,2622,2624,7,2691,2691,7,2786,2787,5,2881,2884,5,3006,3006,5,3072,3072,5,3170,3171,5,3267,3268,7,3330,3331,7,3406,3406,1,3538,3540,5,3655,3662,5,3897,3897,5,4038,4038,5,4184,4185,5,4352,4447,8,6068,6069,5,6155,6157,5,6448,6449,7,6742,6742,5,6783,6783,5,6966,6970,5,7042,7042,7,7143,7143,7,7212,7219,5,7412,7412,5,8206,8207,4,8294,8303,4,8596,8601,14,9410,9410,14,9742,9742,14,9757,9757,14,9770,9770,14,9794,9794,14,9828,9828,14,9855,9855,14,9882,9882,14,9900,9903,14,9929,9933,14,9963,9967,14,9987,9988,14,10006,10006,14,10062,10062,14,10175,10175,14,11744,11775,5,42607,42607,5,43043,43044,7,43263,43263,5,43444,43445,7,43569,43570,5,43698,43700,5,43766,43766,5,44032,44032,11,44144,44144,11,44256,44256,11,44368,44368,11,44480,44480,11,44592,44592,11,44704,44704,11,44816,44816,11,44928,44928,11,45040,45040,11,45152,45152,11,45264,45264,11,45376,45376,11,45488,45488,11,45600,45600,11,45712,45712,11,45824,45824,11,45936,45936,11,46048,46048,11,46160,46160,11,46272,46272,11,46384,46384,11,46496,46496,11,46608,46608,11,46720,46720,11,46832,46832,11,46944,46944,11,47056,47056,11,47168,47168,11,47280,47280,11,47392,47392,11,47504,47504,11,47616,47616,11,47728,47728,11,47840,47840,11,47952,47952,11,48064,48064,11,48176,48176,11,48288,48288,11,48400,48400,11,48512,48512,11,48624,48624,11,48736,48736,11,48848,48848,11,48960,48960,11,49072,49072,11,49184,49184,11,49296,49296,11,49408,49408,11,49520,49520,11,49632,49632,11,49744,49744,11,49856,49856,11,49968,49968,11,50080,50080,11,50192,50192,11,50304,50304,11,50416,50416,11,50528,50528,11,50640,50640,11,50752,50752,11,50864,50864,11,50976,50976,11,51088,51088,11,51200,51200,11,51312,51312,11,51424,51424,11,51536,51536,11,51648,51648,11,51760,51760,11,51872,51872,11,51984,51984,11,52096,52096,11,52208,52208,11,52320,52320,11,52432,52432,11,52544,52544,11,52656,52656,11,52768,52768,11,52880,52880,11,52992,52992,11,53104,53104,11,53216,53216,11,53328,53328,11,53440,53440,11,53552,53552,11,53664,53664,11,53776,53776,11,53888,53888,11,54000,54000,11,54112,54112,11,54224,54224,11,54336,54336,11,54448,54448,11,54560,54560,11,54672,54672,11,54784,54784,11,54896,54896,11,55008,55008,11,55120,55120,11,64286,64286,5,66272,66272,5,68900,68903,5,69762,69762,7,69817,69818,5,69927,69931,5,70003,70003,5,70070,70078,5,70094,70094,7,70194,70195,7,70206,70206,5,70400,70401,5,70463,70463,7,70475,70477,7,70512,70516,5,70722,70724,5,70832,70832,5,70842,70842,5,70847,70848,5,71088,71089,7,71102,71102,7,71219,71226,5,71231,71232,5,71342,71343,7,71453,71455,5,71463,71467,5,71737,71738,5,71995,71996,5,72000,72000,7,72145,72147,7,72160,72160,5,72249,72249,7,72273,72278,5,72330,72342,5,72752,72758,5,72850,72871,5,72882,72883,5,73018,73018,5,73031,73031,5,73109,73109,5,73461,73462,7,94031,94031,5,94192,94193,7,119142,119142,7,119155,119162,4,119362,119364,5,121476,121476,5,122888,122904,5,123184,123190,5,126976,126979,14,127184,127231,14,127344,127345,14,127405,127461,14,127514,127514,14,127561,127567,14,127778,127779,14,127896,127896,14,127985,127986,14,127995,127999,5,128326,128328,14,128360,128366,14,128378,128378,14,128394,128397,14,128405,128406,14,128422,128423,14,128435,128443,14,128453,128464,14,128479,128480,14,128484,128487,14,128496,128498,14,128640,128709,14,128723,128724,14,128736,128741,14,128747,128748,14,128755,128755,14,128762,128762,14,128981,128991,14,129096,129103,14,129292,129292,14,129311,129311,14,129329,129330,14,129344,129349,14,129360,129374,14,129394,129394,14,129402,129402,14,129413,129425,14,129445,129450,14,129466,129471,14,129483,129483,14,129511,129535,14,129653,129655,14,129667,129670,14,129705,129711,14,129731,129743,14,917505,917505,4,917760,917999,5,10,10,3,127,159,4,768,879,5,1471,1471,5,1536,1541,1,1648,1648,5,1767,1768,5,1840,1866,5,2070,2073,5,2137,2139,5,2307,2307,7,2366,2368,7,2382,2383,7,2434,2435,7,2497,2500,5,2519,2519,5,2563,2563,7,2631,2632,5,2677,2677,5,2750,2752,7,2763,2764,7,2817,2817,5,2879,2879,5,2891,2892,7,2914,2915,5,3008,3008,5,3021,3021,5,3076,3076,5,3146,3149,5,3202,3203,7,3264,3265,7,3271,3272,7,3298,3299,5,3390,3390,5,3402,3404,7,3426,3427,5,3535,3535,5,3544,3550,7,3635,3635,7,3763,3763,7,3893,3893,5,3953,3966,5,3981,3991,5,4145,4145,7,4157,4158,5,4209,4212,5,4237,4237,5,4520,4607,10,5970,5971,5,6071,6077,5,6089,6099,5,6277,6278,5,6439,6440,5,6451,6456,7,6683,6683,5,6744,6750,5,6765,6770,7,6846,6846,5,6964,6964,5,6972,6972,5,7019,7027,5,7074,7077,5,7083,7085,5,7146,7148,7,7154,7155,7,7222,7223,5,7394,7400,5,7416,7417,5,8204,8204,5,8233,8233,4,8288,8292,4,8413,8416,5,8482,8482,14,8986,8987,14,9193,9203,14,9654,9654,14,9733,9733,14,9745,9745,14,9752,9752,14,9760,9760,14,9766,9766,14,9774,9775,14,9792,9792,14,9800,9811,14,9825,9826,14,9831,9831,14,9852,9853,14,9872,9873,14,9880,9880,14,9885,9887,14,9896,9897,14,9906,9916,14,9926,9927,14,9936,9936,14,9941,9960,14,9974,9974,14,9982,9985,14,9992,9997,14,10002,10002,14,10017,10017,14,10055,10055,14,10071,10071,14,10145,10145,14,11013,11015,14,11503,11505,5,12334,12335,5,12951,12951,14,42612,42621,5,43014,43014,5,43047,43047,7,43204,43205,5,43335,43345,5,43395,43395,7,43450,43451,7,43561,43566,5,43573,43574,5,43644,43644,5,43710,43711,5,43758,43759,7,44005,44005,5,44012,44012,7,44060,44060,11,44116,44116,11,44172,44172,11,44228,44228,11,44284,44284,11,44340,44340,11,44396,44396,11,44452,44452,11,44508,44508,11,44564,44564,11,44620,44620,11,44676,44676,11,44732,44732,11,44788,44788,11,44844,44844,11,44900,44900,11,44956,44956,11,45012,45012,11,45068,45068,11,45124,45124,11,45180,45180,11,45236,45236,11,45292,45292,11,45348,45348,11,45404,45404,11,45460,45460,11,45516,45516,11,45572,45572,11,45628,45628,11,45684,45684,11,45740,45740,11,45796,45796,11,45852,45852,11,45908,45908,11,45964,45964,11,46020,46020,11,46076,46076,11,46132,46132,11,46188,46188,11,46244,46244,11,46300,46300,11,46356,46356,11,46412,46412,11,46468,46468,11,46524,46524,11,46580,46580,11,46636,46636,11,46692,46692,11,46748,46748,11,46804,46804,11,46860,46860,11,46916,46916,11,46972,46972,11,47028,47028,11,47084,47084,11,47140,47140,11,47196,47196,11,47252,47252,11,47308,47308,11,47364,47364,11,47420,47420,11,47476,47476,11,47532,47532,11,47588,47588,11,47644,47644,11,47700,47700,11,47756,47756,11,47812,47812,11,47868,47868,11,47924,47924,11,47980,47980,11,48036,48036,11,48092,48092,11,48148,48148,11,48204,48204,11,48260,48260,11,48316,48316,11,48372,48372,11,48428,48428,11,48484,48484,11,48540,48540,11,48596,48596,11,48652,48652,11,48708,48708,11,48764,48764,11,48820,48820,11,48876,48876,11,48932,48932,11,48988,48988,11,49044,49044,11,49100,49100,11,49156,49156,11,49212,49212,11,49268,49268,11,49324,49324,11,49380,49380,11,49436,49436,11,49492,49492,11,49548,49548,11,49604,49604,11,49660,49660,11,49716,49716,11,49772,49772,11,49828,49828,11,49884,49884,11,49940,49940,11,49996,49996,11,50052,50052,11,50108,50108,11,50164,50164,11,50220,50220,11,50276,50276,11,50332,50332,11,50388,50388,11,50444,50444,11,50500,50500,11,50556,50556,11,50612,50612,11,50668,50668,11,50724,50724,11,50780,50780,11,50836,50836,11,50892,50892,11,50948,50948,11,51004,51004,11,51060,51060,11,51116,51116,11,51172,51172,11,51228,51228,11,51284,51284,11,51340,51340,11,51396,51396,11,51452,51452,11,51508,51508,11,51564,51564,11,51620,51620,11,51676,51676,11,51732,51732,11,51788,51788,11,51844,51844,11,51900,51900,11,51956,51956,11,52012,52012,11,52068,52068,11,52124,52124,11,52180,52180,11,52236,52236,11,52292,52292,11,52348,52348,11,52404,52404,11,52460,52460,11,52516,52516,11,52572,52572,11,52628,52628,11,52684,52684,11,52740,52740,11,52796,52796,11,52852,52852,11,52908,52908,11,52964,52964,11,53020,53020,11,53076,53076,11,53132,53132,11,53188,53188,11,53244,53244,11,53300,53300,11,53356,53356,11,53412,53412,11,53468,53468,11,53524,53524,11,53580,53580,11,53636,53636,11,53692,53692,11,53748,53748,11,53804,53804,11,53860,53860,11,53916,53916,11,53972,53972,11,54028,54028,11,54084,54084,11,54140,54140,11,54196,54196,11,54252,54252,11,54308,54308,11,54364,54364,11,54420,54420,11,54476,54476,11,54532,54532,11,54588,54588,11,54644,54644,11,54700,54700,11,54756,54756,11,54812,54812,11,54868,54868,11,54924,54924,11,54980,54980,11,55036,55036,11,55092,55092,11,55148,55148,11,55216,55238,9,65056,65071,5,65529,65531,4,68097,68099,5,68159,68159,5,69446,69456,5,69688,69702,5,69808,69810,7,69815,69816,7,69821,69821,1,69888,69890,5,69932,69932,7,69957,69958,7,70016,70017,5,70067,70069,7,70079,70080,7,70089,70092,5,70095,70095,5,70191,70193,5,70196,70196,5,70198,70199,5,70367,70367,5,70371,70378,5,70402,70403,7,70462,70462,5,70464,70464,5,70471,70472,7,70487,70487,5,70502,70508,5,70709,70711,7,70720,70721,7,70725,70725,7,70750,70750,5,70833,70834,7,70841,70841,7,70843,70844,7,70846,70846,7,70849,70849,7,71087,71087,5,71090,71093,5,71100,71101,5,71103,71104,5,71216,71218,7,71227,71228,7,71230,71230,7,71339,71339,5,71341,71341,5,71344,71349,5,71351,71351,5,71456,71457,7,71462,71462,7,71724,71726,7,71736,71736,7,71984,71984,5,71991,71992,7,71997,71997,7,71999,71999,1,72001,72001,1,72003,72003,5,72148,72151,5,72156,72159,7,72164,72164,7,72243,72248,5,72250,72250,1,72263,72263,5,72279,72280,7,72324,72329,1,72343,72343,7,72751,72751,7,72760,72765,5,72767,72767,5,72873,72873,7,72881,72881,7,72884,72884,7,73009,73014,5,73020,73021,5,73030,73030,1,73098,73102,7,73107,73108,7,73110,73110,7,73459,73460,5,78896,78904,4,92976,92982,5,94033,94087,7,94180,94180,5,113821,113822,5,119141,119141,5,119143,119145,5,119150,119154,5,119163,119170,5,119210,119213,5,121344,121398,5,121461,121461,5,121499,121503,5,122880,122886,5,122907,122913,5,122918,122922,5,123628,123631,5,125252,125258,5,126980,126980,14,127183,127183,14,127245,127247,14,127340,127343,14,127358,127359,14,127377,127386,14,127462,127487,6,127491,127503,14,127535,127535,14,127548,127551,14,127568,127569,14,127744,127777,14,127780,127891,14,127894,127895,14,127897,127899,14,127902,127984,14,127987,127989,14,127991,127994,14,128000,128253,14,128255,128317,14,128329,128334,14,128336,128359,14,128367,128368,14,128371,128377,14,128379,128390,14,128392,128393,14,128398,128399,14,128401,128404,14,128407,128419,14,128421,128421,14,128424,128424,14,128433,128434,14,128444,128444,14,128450,128452,14,128465,128467,14,128476,128478,14,128481,128481,14,128483,128483,14,128488,128488,14,128495,128495,14,128499,128499,14,128506,128591,14,128710,128714,14,128721,128722,14,128725,128725,14,128728,128735,14,128742,128744,14,128746,128746,14,128749,128751,14,128753,128754,14,128756,128758,14,128761,128761,14,128763,128764,14,128884,128895,14,128992,129003,14,129036,129039,14,129114,129119,14,129198,129279,14,129293,129295,14,129305,129310,14,129312,129319,14,129328,129328,14,129331,129338,14,129343,129343,14,129351,129355,14,129357,129359,14,129375,129387,14,129393,129393,14,129395,129398,14,129401,129401,14,129403,129403,14,129408,129412,14,129426,129431,14,129443,129444,14,129451,129453,14,129456,129465,14,129472,129472,14,129475,129482,14,129484,129484,14,129488,129510,14,129536,129647,14,129652,129652,14,129656,129658,14,129664,129666,14,129671,129679,14,129686,129704,14,129712,129718,14,129728,129730,14,129744,129750,14,917504,917504,4,917506,917535,4,917632,917759,4,918000,921599,4,0,9,4,11,12,4,14,31,4,169,169,14,174,174,14,1155,1159,5,1425,1469,5,1473,1474,5,1479,1479,5,1552,1562,5,1611,1631,5,1750,1756,5,1759,1764,5,1770,1773,5,1809,1809,5,1958,1968,5,2045,2045,5,2075,2083,5,2089,2093,5,2259,2273,5,2275,2306,5,2362,2362,5,2364,2364,5,2369,2376,5,2381,2381,5,2385,2391,5,2433,2433,5,2492,2492,5,2495,2496,7,2503,2504,7,2509,2509,5,2530,2531,5,2561,2562,5,2620,2620,5,2625,2626,5,2635,2637,5,2672,2673,5,2689,2690,5,2748,2748,5,2753,2757,5,2761,2761,7,2765,2765,5,2810,2815,5,2818,2819,7,2878,2878,5,2880,2880,7,2887,2888,7,2893,2893,5,2903,2903,5,2946,2946,5,3007,3007,7,3009,3010,7,3018,3020,7,3031,3031,5,3073,3075,7,3134,3136,5,3142,3144,5,3157,3158,5,3201,3201,5,3260,3260,5,3263,3263,5,3266,3266,5,3270,3270,5,3274,3275,7,3285,3286,5,3328,3329,5,3387,3388,5,3391,3392,7,3398,3400,7,3405,3405,5,3415,3415,5,3457,3457,5,3530,3530,5,3536,3537,7,3542,3542,5,3551,3551,5,3633,3633,5,3636,3642,5,3761,3761,5,3764,3772,5,3864,3865,5,3895,3895,5,3902,3903,7,3967,3967,7,3974,3975,5,3993,4028,5,4141,4144,5,4146,4151,5,4155,4156,7,4182,4183,7,4190,4192,5,4226,4226,5,4229,4230,5,4253,4253,5,4448,4519,9,4957,4959,5,5938,5940,5,6002,6003,5,6070,6070,7,6078,6085,7,6087,6088,7,6109,6109,5,6158,6158,4,6313,6313,5,6435,6438,7,6441,6443,7,6450,6450,5,6457,6459,5,6681,6682,7,6741,6741,7,6743,6743,7,6752,6752,5,6757,6764,5,6771,6780,5,6832,6845,5,6847,6848,5,6916,6916,7,6965,6965,5,6971,6971,7,6973,6977,7,6979,6980,7,7040,7041,5,7073,7073,7,7078,7079,7,7082,7082,7,7142,7142,5,7144,7145,5,7149,7149,5,7151,7153,5,7204,7211,7,7220,7221,7,7376,7378,5,7393,7393,7,7405,7405,5,7415,7415,7,7616,7673,5,8203,8203,4,8205,8205,13,8232,8232,4,8234,8238,4,8265,8265,14,8293,8293,4,8400,8412,5,8417,8417,5,8421,8432,5,8505,8505,14,8617,8618,14,9000,9000,14,9167,9167,14,9208,9210,14,9642,9643,14,9664,9664,14,9728,9732,14,9735,9741,14,9743,9744,14,9746,9746,14,9750,9751,14,9753,9756,14,9758,9759,14,9761,9761,14,9764,9765,14,9767,9769,14,9771,9773,14,9776,9783,14,9787,9791,14,9793,9793,14,9795,9799,14,9812,9822,14,9824,9824,14,9827,9827,14,9829,9830,14,9832,9832,14,9851,9851,14,9854,9854,14,9856,9861,14,9874,9876,14,9878,9879,14,9881,9881,14,9883,9884,14,9888,9889,14,9895,9895,14,9898,9899,14,9904,9905,14,9917,9918,14,9924,9925,14,9928,9928,14,9934,9935,14,9937,9937,14,9939,9940,14,9961,9962,14,9968,9973,14,9975,9978,14,9981,9981,14,9986,9986,14,9989,9989,14,9998,9998,14,10000,10001,14,10004,10004,14,10013,10013,14,10024,10024,14,10052,10052,14,10060,10060,14,10067,10069,14,10083,10084,14,10133,10135,14,10160,10160,14,10548,10549,14,11035,11036,14,11093,11093,14,11647,11647,5,12330,12333,5,12336,12336,14,12441,12442,5,12953,12953,14,42608,42610,5,42654,42655,5,43010,43010,5,43019,43019,5,43045,43046,5,43052,43052,5,43188,43203,7,43232,43249,5,43302,43309,5,43346,43347,7,43392,43394,5,43443,43443,5,43446,43449,5,43452,43453,5,43493,43493,5,43567,43568,7,43571,43572,7,43587,43587,5,43597,43597,7,43696,43696,5,43703,43704,5,43713,43713,5,43756,43757,5,43765,43765,7,44003,44004,7,44006,44007,7,44009,44010,7,44013,44013,5,44033,44059,12,44061,44087,12,44089,44115,12,44117,44143,12,44145,44171,12,44173,44199,12,44201,44227,12,44229,44255,12,44257,44283,12,44285,44311,12,44313,44339,12,44341,44367,12,44369,44395,12,44397,44423,12,44425,44451,12,44453,44479,12,44481,44507,12,44509,44535,12,44537,44563,12,44565,44591,12,44593,44619,12,44621,44647,12,44649,44675,12,44677,44703,12,44705,44731,12,44733,44759,12,44761,44787,12,44789,44815,12,44817,44843,12,44845,44871,12,44873,44899,12,44901,44927,12,44929,44955,12,44957,44983,12,44985,45011,12,45013,45039,12,45041,45067,12,45069,45095,12,45097,45123,12,45125,45151,12,45153,45179,12,45181,45207,12,45209,45235,12,45237,45263,12,45265,45291,12,45293,45319,12,45321,45347,12,45349,45375,12,45377,45403,12,45405,45431,12,45433,45459,12,45461,45487,12,45489,45515,12,45517,45543,12,45545,45571,12,45573,45599,12,45601,45627,12,45629,45655,12,45657,45683,12,45685,45711,12,45713,45739,12,45741,45767,12,45769,45795,12,45797,45823,12,45825,45851,12,45853,45879,12,45881,45907,12,45909,45935,12,45937,45963,12,45965,45991,12,45993,46019,12,46021,46047,12,46049,46075,12,46077,46103,12,46105,46131,12,46133,46159,12,46161,46187,12,46189,46215,12,46217,46243,12,46245,46271,12,46273,46299,12,46301,46327,12,46329,46355,12,46357,46383,12,46385,46411,12,46413,46439,12,46441,46467,12,46469,46495,12,46497,46523,12,46525,46551,12,46553,46579,12,46581,46607,12,46609,46635,12,46637,46663,12,46665,46691,12,46693,46719,12,46721,46747,12,46749,46775,12,46777,46803,12,46805,46831,12,46833,46859,12,46861,46887,12,46889,46915,12,46917,46943,12,46945,46971,12,46973,46999,12,47001,47027,12,47029,47055,12,47057,47083,12,47085,47111,12,47113,47139,12,47141,47167,12,47169,47195,12,47197,47223,12,47225,47251,12,47253,47279,12,47281,47307,12,47309,47335,12,47337,47363,12,47365,47391,12,47393,47419,12,47421,47447,12,47449,47475,12,47477,47503,12,47505,47531,12,47533,47559,12,47561,47587,12,47589,47615,12,47617,47643,12,47645,47671,12,47673,47699,12,47701,47727,12,47729,47755,12,47757,47783,12,47785,47811,12,47813,47839,12,47841,47867,12,47869,47895,12,47897,47923,12,47925,47951,12,47953,47979,12,47981,48007,12,48009,48035,12,48037,48063,12,48065,48091,12,48093,48119,12,48121,48147,12,48149,48175,12,48177,48203,12,48205,48231,12,48233,48259,12,48261,48287,12,48289,48315,12,48317,48343,12,48345,48371,12,48373,48399,12,48401,48427,12,48429,48455,12,48457,48483,12,48485,48511,12,48513,48539,12,48541,48567,12,48569,48595,12,48597,48623,12,48625,48651,12,48653,48679,12,48681,48707,12,48709,48735,12,48737,48763,12,48765,48791,12,48793,48819,12,48821,48847,12,48849,48875,12,48877,48903,12,48905,48931,12,48933,48959,12,48961,48987,12,48989,49015,12,49017,49043,12,49045,49071,12,49073,49099,12,49101,49127,12,49129,49155,12,49157,49183,12,49185,49211,12,49213,49239,12,49241,49267,12,49269,49295,12,49297,49323,12,49325,49351,12,49353,49379,12,49381,49407,12,49409,49435,12,49437,49463,12,49465,49491,12,49493,49519,12,49521,49547,12,49549,49575,12,49577,49603,12,49605,49631,12,49633,49659,12,49661,49687,12,49689,49715,12,49717,49743,12,49745,49771,12,49773,49799,12,49801,49827,12,49829,49855,12,49857,49883,12,49885,49911,12,49913,49939,12,49941,49967,12,49969,49995,12,49997,50023,12,50025,50051,12,50053,50079,12,50081,50107,12,50109,50135,12,50137,50163,12,50165,50191,12,50193,50219,12,50221,50247,12,50249,50275,12,50277,50303,12,50305,50331,12,50333,50359,12,50361,50387,12,50389,50415,12,50417,50443,12,50445,50471,12,50473,50499,12,50501,50527,12,50529,50555,12,50557,50583,12,50585,50611,12,50613,50639,12,50641,50667,12,50669,50695,12,50697,50723,12,50725,50751,12,50753,50779,12,50781,50807,12,50809,50835,12,50837,50863,12,50865,50891,12,50893,50919,12,50921,50947,12,50949,50975,12,50977,51003,12,51005,51031,12,51033,51059,12,51061,51087,12,51089,51115,12,51117,51143,12,51145,51171,12,51173,51199,12,51201,51227,12,51229,51255,12,51257,51283,12,51285,51311,12,51313,51339,12,51341,51367,12,51369,51395,12,51397,51423,12,51425,51451,12,51453,51479,12,51481,51507,12,51509,51535,12,51537,51563,12,51565,51591,12,51593,51619,12,51621,51647,12,51649,51675,12,51677,51703,12,51705,51731,12,51733,51759,12,51761,51787,12,51789,51815,12,51817,51843,12,51845,51871,12,51873,51899,12,51901,51927,12,51929,51955,12,51957,51983,12,51985,52011,12,52013,52039,12,52041,52067,12,52069,52095,12,52097,52123,12,52125,52151,12,52153,52179,12,52181,52207,12,52209,52235,12,52237,52263,12,52265,52291,12,52293,52319,12,52321,52347,12,52349,52375,12,52377,52403,12,52405,52431,12,52433,52459,12,52461,52487,12,52489,52515,12,52517,52543,12,52545,52571,12,52573,52599,12,52601,52627,12,52629,52655,12,52657,52683,12,52685,52711,12,52713,52739,12,52741,52767,12,52769,52795,12,52797,52823,12,52825,52851,12,52853,52879,12,52881,52907,12,52909,52935,12,52937,52963,12,52965,52991,12,52993,53019,12,53021,53047,12,53049,53075,12,53077,53103,12,53105,53131,12,53133,53159,12,53161,53187,12,53189,53215,12,53217,53243,12,53245,53271,12,53273,53299,12,53301,53327,12,53329,53355,12,53357,53383,12,53385,53411,12,53413,53439,12,53441,53467,12,53469,53495,12,53497,53523,12,53525,53551,12,53553,53579,12,53581,53607,12,53609,53635,12,53637,53663,12,53665,53691,12,53693,53719,12,53721,53747,12,53749,53775,12,53777,53803,12,53805,53831,12,53833,53859,12,53861,53887,12,53889,53915,12,53917,53943,12,53945,53971,12,53973,53999,12,54001,54027,12,54029,54055,12,54057,54083,12,54085,54111,12,54113,54139,12,54141,54167,12,54169,54195,12,54197,54223,12,54225,54251,12,54253,54279,12,54281,54307,12,54309,54335,12,54337,54363,12,54365,54391,12,54393,54419,12,54421,54447,12,54449,54475,12,54477,54503,12,54505,54531,12,54533,54559,12,54561,54587,12,54589,54615,12,54617,54643,12,54645,54671,12,54673,54699,12,54701,54727,12,54729,54755,12,54757,54783,12,54785,54811,12,54813,54839,12,54841,54867,12,54869,54895,12,54897,54923,12,54925,54951,12,54953,54979,12,54981,55007,12,55009,55035,12,55037,55063,12,55065,55091,12,55093,55119,12,55121,55147,12,55149,55175,12,55177,55203,12,55243,55291,10,65024,65039,5,65279,65279,4,65520,65528,4,66045,66045,5,66422,66426,5,68101,68102,5,68152,68154,5,68325,68326,5,69291,69292,5,69632,69632,7,69634,69634,7,69759,69761,5]'); + // generated using https://github.com/alexdima/unicode-utils/blob/main/grapheme-break.js + return JSON.parse('[0,0,0,51229,51255,12,44061,44087,12,127462,127487,6,7083,7085,5,47645,47671,12,54813,54839,12,128678,128678,14,3270,3270,5,9919,9923,14,45853,45879,12,49437,49463,12,53021,53047,12,71216,71218,7,128398,128399,14,129360,129374,14,2519,2519,5,4448,4519,9,9742,9742,14,12336,12336,14,44957,44983,12,46749,46775,12,48541,48567,12,50333,50359,12,52125,52151,12,53917,53943,12,69888,69890,5,73018,73018,5,127990,127990,14,128558,128559,14,128759,128760,14,129653,129655,14,2027,2035,5,2891,2892,7,3761,3761,5,6683,6683,5,8293,8293,4,9825,9826,14,9999,9999,14,43452,43453,5,44509,44535,12,45405,45431,12,46301,46327,12,47197,47223,12,48093,48119,12,48989,49015,12,49885,49911,12,50781,50807,12,51677,51703,12,52573,52599,12,53469,53495,12,54365,54391,12,65279,65279,4,70471,70472,7,72145,72147,7,119173,119179,5,127799,127818,14,128240,128244,14,128512,128512,14,128652,128652,14,128721,128722,14,129292,129292,14,129445,129450,14,129734,129743,14,1476,1477,5,2366,2368,7,2750,2752,7,3076,3076,5,3415,3415,5,4141,4144,5,6109,6109,5,6964,6964,5,7394,7400,5,9197,9198,14,9770,9770,14,9877,9877,14,9968,9969,14,10084,10084,14,43052,43052,5,43713,43713,5,44285,44311,12,44733,44759,12,45181,45207,12,45629,45655,12,46077,46103,12,46525,46551,12,46973,46999,12,47421,47447,12,47869,47895,12,48317,48343,12,48765,48791,12,49213,49239,12,49661,49687,12,50109,50135,12,50557,50583,12,51005,51031,12,51453,51479,12,51901,51927,12,52349,52375,12,52797,52823,12,53245,53271,12,53693,53719,12,54141,54167,12,54589,54615,12,55037,55063,12,69506,69509,5,70191,70193,5,70841,70841,7,71463,71467,5,72330,72342,5,94031,94031,5,123628,123631,5,127763,127765,14,127941,127941,14,128043,128062,14,128302,128317,14,128465,128467,14,128539,128539,14,128640,128640,14,128662,128662,14,128703,128703,14,128745,128745,14,129004,129007,14,129329,129330,14,129402,129402,14,129483,129483,14,129686,129704,14,130048,131069,14,173,173,4,1757,1757,1,2200,2207,5,2434,2435,7,2631,2632,5,2817,2817,5,3008,3008,5,3201,3201,5,3387,3388,5,3542,3542,5,3902,3903,7,4190,4192,5,6002,6003,5,6439,6440,5,6765,6770,7,7019,7027,5,7154,7155,7,8205,8205,13,8505,8505,14,9654,9654,14,9757,9757,14,9792,9792,14,9852,9853,14,9890,9894,14,9937,9937,14,9981,9981,14,10035,10036,14,11035,11036,14,42654,42655,5,43346,43347,7,43587,43587,5,44006,44007,7,44173,44199,12,44397,44423,12,44621,44647,12,44845,44871,12,45069,45095,12,45293,45319,12,45517,45543,12,45741,45767,12,45965,45991,12,46189,46215,12,46413,46439,12,46637,46663,12,46861,46887,12,47085,47111,12,47309,47335,12,47533,47559,12,47757,47783,12,47981,48007,12,48205,48231,12,48429,48455,12,48653,48679,12,48877,48903,12,49101,49127,12,49325,49351,12,49549,49575,12,49773,49799,12,49997,50023,12,50221,50247,12,50445,50471,12,50669,50695,12,50893,50919,12,51117,51143,12,51341,51367,12,51565,51591,12,51789,51815,12,52013,52039,12,52237,52263,12,52461,52487,12,52685,52711,12,52909,52935,12,53133,53159,12,53357,53383,12,53581,53607,12,53805,53831,12,54029,54055,12,54253,54279,12,54477,54503,12,54701,54727,12,54925,54951,12,55149,55175,12,68101,68102,5,69762,69762,7,70067,70069,7,70371,70378,5,70720,70721,7,71087,71087,5,71341,71341,5,71995,71996,5,72249,72249,7,72850,72871,5,73109,73109,5,118576,118598,5,121505,121519,5,127245,127247,14,127568,127569,14,127777,127777,14,127872,127891,14,127956,127967,14,128015,128016,14,128110,128172,14,128259,128259,14,128367,128368,14,128424,128424,14,128488,128488,14,128530,128532,14,128550,128551,14,128566,128566,14,128647,128647,14,128656,128656,14,128667,128673,14,128691,128693,14,128715,128715,14,128728,128732,14,128752,128752,14,128765,128767,14,129096,129103,14,129311,129311,14,129344,129349,14,129394,129394,14,129413,129425,14,129466,129471,14,129511,129535,14,129664,129666,14,129719,129722,14,129760,129767,14,917536,917631,5,13,13,2,1160,1161,5,1564,1564,4,1807,1807,1,2085,2087,5,2307,2307,7,2382,2383,7,2497,2500,5,2563,2563,7,2677,2677,5,2763,2764,7,2879,2879,5,2914,2915,5,3021,3021,5,3142,3144,5,3263,3263,5,3285,3286,5,3398,3400,7,3530,3530,5,3633,3633,5,3864,3865,5,3974,3975,5,4155,4156,7,4229,4230,5,5909,5909,7,6078,6085,7,6277,6278,5,6451,6456,7,6744,6750,5,6846,6846,5,6972,6972,5,7074,7077,5,7146,7148,7,7222,7223,5,7416,7417,5,8234,8238,4,8417,8417,5,9000,9000,14,9203,9203,14,9730,9731,14,9748,9749,14,9762,9763,14,9776,9783,14,9800,9811,14,9831,9831,14,9872,9873,14,9882,9882,14,9900,9903,14,9929,9933,14,9941,9960,14,9974,9974,14,9989,9989,14,10006,10006,14,10062,10062,14,10160,10160,14,11647,11647,5,12953,12953,14,43019,43019,5,43232,43249,5,43443,43443,5,43567,43568,7,43696,43696,5,43765,43765,7,44013,44013,5,44117,44143,12,44229,44255,12,44341,44367,12,44453,44479,12,44565,44591,12,44677,44703,12,44789,44815,12,44901,44927,12,45013,45039,12,45125,45151,12,45237,45263,12,45349,45375,12,45461,45487,12,45573,45599,12,45685,45711,12,45797,45823,12,45909,45935,12,46021,46047,12,46133,46159,12,46245,46271,12,46357,46383,12,46469,46495,12,46581,46607,12,46693,46719,12,46805,46831,12,46917,46943,12,47029,47055,12,47141,47167,12,47253,47279,12,47365,47391,12,47477,47503,12,47589,47615,12,47701,47727,12,47813,47839,12,47925,47951,12,48037,48063,12,48149,48175,12,48261,48287,12,48373,48399,12,48485,48511,12,48597,48623,12,48709,48735,12,48821,48847,12,48933,48959,12,49045,49071,12,49157,49183,12,49269,49295,12,49381,49407,12,49493,49519,12,49605,49631,12,49717,49743,12,49829,49855,12,49941,49967,12,50053,50079,12,50165,50191,12,50277,50303,12,50389,50415,12,50501,50527,12,50613,50639,12,50725,50751,12,50837,50863,12,50949,50975,12,51061,51087,12,51173,51199,12,51285,51311,12,51397,51423,12,51509,51535,12,51621,51647,12,51733,51759,12,51845,51871,12,51957,51983,12,52069,52095,12,52181,52207,12,52293,52319,12,52405,52431,12,52517,52543,12,52629,52655,12,52741,52767,12,52853,52879,12,52965,52991,12,53077,53103,12,53189,53215,12,53301,53327,12,53413,53439,12,53525,53551,12,53637,53663,12,53749,53775,12,53861,53887,12,53973,53999,12,54085,54111,12,54197,54223,12,54309,54335,12,54421,54447,12,54533,54559,12,54645,54671,12,54757,54783,12,54869,54895,12,54981,55007,12,55093,55119,12,55243,55291,10,66045,66045,5,68325,68326,5,69688,69702,5,69817,69818,5,69957,69958,7,70089,70092,5,70198,70199,5,70462,70462,5,70502,70508,5,70750,70750,5,70846,70846,7,71100,71101,5,71230,71230,7,71351,71351,5,71737,71738,5,72000,72000,7,72160,72160,5,72273,72278,5,72752,72758,5,72882,72883,5,73031,73031,5,73461,73462,7,94192,94193,7,119149,119149,7,121403,121452,5,122915,122916,5,126980,126980,14,127358,127359,14,127535,127535,14,127759,127759,14,127771,127771,14,127792,127793,14,127825,127867,14,127897,127899,14,127945,127945,14,127985,127986,14,128000,128007,14,128021,128021,14,128066,128100,14,128184,128235,14,128249,128252,14,128266,128276,14,128335,128335,14,128379,128390,14,128407,128419,14,128444,128444,14,128481,128481,14,128499,128499,14,128526,128526,14,128536,128536,14,128543,128543,14,128556,128556,14,128564,128564,14,128577,128580,14,128643,128645,14,128649,128649,14,128654,128654,14,128660,128660,14,128664,128664,14,128675,128675,14,128686,128689,14,128695,128696,14,128705,128709,14,128717,128719,14,128725,128725,14,128736,128741,14,128747,128748,14,128755,128755,14,128762,128762,14,128981,128991,14,129009,129023,14,129160,129167,14,129296,129304,14,129320,129327,14,129340,129342,14,129356,129356,14,129388,129392,14,129399,129400,14,129404,129407,14,129432,129442,14,129454,129455,14,129473,129474,14,129485,129487,14,129648,129651,14,129659,129660,14,129671,129679,14,129709,129711,14,129728,129730,14,129751,129753,14,129776,129782,14,917505,917505,4,917760,917999,5,10,10,3,127,159,4,768,879,5,1471,1471,5,1536,1541,1,1648,1648,5,1767,1768,5,1840,1866,5,2070,2073,5,2137,2139,5,2274,2274,1,2363,2363,7,2377,2380,7,2402,2403,5,2494,2494,5,2507,2508,7,2558,2558,5,2622,2624,7,2641,2641,5,2691,2691,7,2759,2760,5,2786,2787,5,2876,2876,5,2881,2884,5,2901,2902,5,3006,3006,5,3014,3016,7,3072,3072,5,3134,3136,5,3157,3158,5,3260,3260,5,3266,3266,5,3274,3275,7,3328,3329,5,3391,3392,7,3405,3405,5,3457,3457,5,3536,3537,7,3551,3551,5,3636,3642,5,3764,3772,5,3895,3895,5,3967,3967,7,3993,4028,5,4146,4151,5,4182,4183,7,4226,4226,5,4253,4253,5,4957,4959,5,5940,5940,7,6070,6070,7,6087,6088,7,6158,6158,4,6432,6434,5,6448,6449,7,6679,6680,5,6742,6742,5,6754,6754,5,6783,6783,5,6912,6915,5,6966,6970,5,6978,6978,5,7042,7042,7,7080,7081,5,7143,7143,7,7150,7150,7,7212,7219,5,7380,7392,5,7412,7412,5,8203,8203,4,8232,8232,4,8265,8265,14,8400,8412,5,8421,8432,5,8617,8618,14,9167,9167,14,9200,9200,14,9410,9410,14,9723,9726,14,9733,9733,14,9745,9745,14,9752,9752,14,9760,9760,14,9766,9766,14,9774,9774,14,9786,9786,14,9794,9794,14,9823,9823,14,9828,9828,14,9833,9850,14,9855,9855,14,9875,9875,14,9880,9880,14,9885,9887,14,9896,9897,14,9906,9916,14,9926,9927,14,9935,9935,14,9939,9939,14,9962,9962,14,9972,9972,14,9978,9978,14,9986,9986,14,9997,9997,14,10002,10002,14,10017,10017,14,10055,10055,14,10071,10071,14,10133,10135,14,10548,10549,14,11093,11093,14,12330,12333,5,12441,12442,5,42608,42610,5,43010,43010,5,43045,43046,5,43188,43203,7,43302,43309,5,43392,43394,5,43446,43449,5,43493,43493,5,43571,43572,7,43597,43597,7,43703,43704,5,43756,43757,5,44003,44004,7,44009,44010,7,44033,44059,12,44089,44115,12,44145,44171,12,44201,44227,12,44257,44283,12,44313,44339,12,44369,44395,12,44425,44451,12,44481,44507,12,44537,44563,12,44593,44619,12,44649,44675,12,44705,44731,12,44761,44787,12,44817,44843,12,44873,44899,12,44929,44955,12,44985,45011,12,45041,45067,12,45097,45123,12,45153,45179,12,45209,45235,12,45265,45291,12,45321,45347,12,45377,45403,12,45433,45459,12,45489,45515,12,45545,45571,12,45601,45627,12,45657,45683,12,45713,45739,12,45769,45795,12,45825,45851,12,45881,45907,12,45937,45963,12,45993,46019,12,46049,46075,12,46105,46131,12,46161,46187,12,46217,46243,12,46273,46299,12,46329,46355,12,46385,46411,12,46441,46467,12,46497,46523,12,46553,46579,12,46609,46635,12,46665,46691,12,46721,46747,12,46777,46803,12,46833,46859,12,46889,46915,12,46945,46971,12,47001,47027,12,47057,47083,12,47113,47139,12,47169,47195,12,47225,47251,12,47281,47307,12,47337,47363,12,47393,47419,12,47449,47475,12,47505,47531,12,47561,47587,12,47617,47643,12,47673,47699,12,47729,47755,12,47785,47811,12,47841,47867,12,47897,47923,12,47953,47979,12,48009,48035,12,48065,48091,12,48121,48147,12,48177,48203,12,48233,48259,12,48289,48315,12,48345,48371,12,48401,48427,12,48457,48483,12,48513,48539,12,48569,48595,12,48625,48651,12,48681,48707,12,48737,48763,12,48793,48819,12,48849,48875,12,48905,48931,12,48961,48987,12,49017,49043,12,49073,49099,12,49129,49155,12,49185,49211,12,49241,49267,12,49297,49323,12,49353,49379,12,49409,49435,12,49465,49491,12,49521,49547,12,49577,49603,12,49633,49659,12,49689,49715,12,49745,49771,12,49801,49827,12,49857,49883,12,49913,49939,12,49969,49995,12,50025,50051,12,50081,50107,12,50137,50163,12,50193,50219,12,50249,50275,12,50305,50331,12,50361,50387,12,50417,50443,12,50473,50499,12,50529,50555,12,50585,50611,12,50641,50667,12,50697,50723,12,50753,50779,12,50809,50835,12,50865,50891,12,50921,50947,12,50977,51003,12,51033,51059,12,51089,51115,12,51145,51171,12,51201,51227,12,51257,51283,12,51313,51339,12,51369,51395,12,51425,51451,12,51481,51507,12,51537,51563,12,51593,51619,12,51649,51675,12,51705,51731,12,51761,51787,12,51817,51843,12,51873,51899,12,51929,51955,12,51985,52011,12,52041,52067,12,52097,52123,12,52153,52179,12,52209,52235,12,52265,52291,12,52321,52347,12,52377,52403,12,52433,52459,12,52489,52515,12,52545,52571,12,52601,52627,12,52657,52683,12,52713,52739,12,52769,52795,12,52825,52851,12,52881,52907,12,52937,52963,12,52993,53019,12,53049,53075,12,53105,53131,12,53161,53187,12,53217,53243,12,53273,53299,12,53329,53355,12,53385,53411,12,53441,53467,12,53497,53523,12,53553,53579,12,53609,53635,12,53665,53691,12,53721,53747,12,53777,53803,12,53833,53859,12,53889,53915,12,53945,53971,12,54001,54027,12,54057,54083,12,54113,54139,12,54169,54195,12,54225,54251,12,54281,54307,12,54337,54363,12,54393,54419,12,54449,54475,12,54505,54531,12,54561,54587,12,54617,54643,12,54673,54699,12,54729,54755,12,54785,54811,12,54841,54867,12,54897,54923,12,54953,54979,12,55009,55035,12,55065,55091,12,55121,55147,12,55177,55203,12,65024,65039,5,65520,65528,4,66422,66426,5,68152,68154,5,69291,69292,5,69633,69633,5,69747,69748,5,69811,69814,5,69826,69826,5,69932,69932,7,70016,70017,5,70079,70080,7,70095,70095,5,70196,70196,5,70367,70367,5,70402,70403,7,70464,70464,5,70487,70487,5,70709,70711,7,70725,70725,7,70833,70834,7,70843,70844,7,70849,70849,7,71090,71093,5,71103,71104,5,71227,71228,7,71339,71339,5,71344,71349,5,71458,71461,5,71727,71735,5,71985,71989,7,71998,71998,5,72002,72002,7,72154,72155,5,72193,72202,5,72251,72254,5,72281,72283,5,72344,72345,5,72766,72766,7,72874,72880,5,72885,72886,5,73023,73029,5,73104,73105,5,73111,73111,5,92912,92916,5,94095,94098,5,113824,113827,4,119142,119142,7,119155,119162,4,119362,119364,5,121476,121476,5,122888,122904,5,123184,123190,5,125252,125258,5,127183,127183,14,127340,127343,14,127377,127386,14,127491,127503,14,127548,127551,14,127744,127756,14,127761,127761,14,127769,127769,14,127773,127774,14,127780,127788,14,127796,127797,14,127820,127823,14,127869,127869,14,127894,127895,14,127902,127903,14,127943,127943,14,127947,127950,14,127972,127972,14,127988,127988,14,127992,127994,14,128009,128011,14,128019,128019,14,128023,128041,14,128064,128064,14,128102,128107,14,128174,128181,14,128238,128238,14,128246,128247,14,128254,128254,14,128264,128264,14,128278,128299,14,128329,128330,14,128348,128359,14,128371,128377,14,128392,128393,14,128401,128404,14,128421,128421,14,128433,128434,14,128450,128452,14,128476,128478,14,128483,128483,14,128495,128495,14,128506,128506,14,128519,128520,14,128528,128528,14,128534,128534,14,128538,128538,14,128540,128542,14,128544,128549,14,128552,128555,14,128557,128557,14,128560,128563,14,128565,128565,14,128567,128576,14,128581,128591,14,128641,128642,14,128646,128646,14,128648,128648,14,128650,128651,14,128653,128653,14,128655,128655,14,128657,128659,14,128661,128661,14,128663,128663,14,128665,128666,14,128674,128674,14,128676,128677,14,128679,128685,14,128690,128690,14,128694,128694,14,128697,128702,14,128704,128704,14,128710,128714,14,128716,128716,14,128720,128720,14,128723,128724,14,128726,128727,14,128733,128735,14,128742,128744,14,128746,128746,14,128749,128751,14,128753,128754,14,128756,128758,14,128761,128761,14,128763,128764,14,128884,128895,14,128992,129003,14,129008,129008,14,129036,129039,14,129114,129119,14,129198,129279,14,129293,129295,14,129305,129310,14,129312,129319,14,129328,129328,14,129331,129338,14,129343,129343,14,129351,129355,14,129357,129359,14,129375,129387,14,129393,129393,14,129395,129398,14,129401,129401,14,129403,129403,14,129408,129412,14,129426,129431,14,129443,129444,14,129451,129453,14,129456,129465,14,129472,129472,14,129475,129482,14,129484,129484,14,129488,129510,14,129536,129647,14,129652,129652,14,129656,129658,14,129661,129663,14,129667,129670,14,129680,129685,14,129705,129708,14,129712,129718,14,129723,129727,14,129731,129733,14,129744,129750,14,129754,129759,14,129768,129775,14,129783,129791,14,917504,917504,4,917506,917535,4,917632,917759,4,918000,921599,4,0,9,4,11,12,4,14,31,4,169,169,14,174,174,14,1155,1159,5,1425,1469,5,1473,1474,5,1479,1479,5,1552,1562,5,1611,1631,5,1750,1756,5,1759,1764,5,1770,1773,5,1809,1809,5,1958,1968,5,2045,2045,5,2075,2083,5,2089,2093,5,2192,2193,1,2250,2273,5,2275,2306,5,2362,2362,5,2364,2364,5,2369,2376,5,2381,2381,5,2385,2391,5,2433,2433,5,2492,2492,5,2495,2496,7,2503,2504,7,2509,2509,5,2530,2531,5,2561,2562,5,2620,2620,5,2625,2626,5,2635,2637,5,2672,2673,5,2689,2690,5,2748,2748,5,2753,2757,5,2761,2761,7,2765,2765,5,2810,2815,5,2818,2819,7,2878,2878,5,2880,2880,7,2887,2888,7,2893,2893,5,2903,2903,5,2946,2946,5,3007,3007,7,3009,3010,7,3018,3020,7,3031,3031,5,3073,3075,7,3132,3132,5,3137,3140,7,3146,3149,5,3170,3171,5,3202,3203,7,3262,3262,7,3264,3265,7,3267,3268,7,3271,3272,7,3276,3277,5,3298,3299,5,3330,3331,7,3390,3390,5,3393,3396,5,3402,3404,7,3406,3406,1,3426,3427,5,3458,3459,7,3535,3535,5,3538,3540,5,3544,3550,7,3570,3571,7,3635,3635,7,3655,3662,5,3763,3763,7,3784,3789,5,3893,3893,5,3897,3897,5,3953,3966,5,3968,3972,5,3981,3991,5,4038,4038,5,4145,4145,7,4153,4154,5,4157,4158,5,4184,4185,5,4209,4212,5,4228,4228,7,4237,4237,5,4352,4447,8,4520,4607,10,5906,5908,5,5938,5939,5,5970,5971,5,6068,6069,5,6071,6077,5,6086,6086,5,6089,6099,5,6155,6157,5,6159,6159,5,6313,6313,5,6435,6438,7,6441,6443,7,6450,6450,5,6457,6459,5,6681,6682,7,6741,6741,7,6743,6743,7,6752,6752,5,6757,6764,5,6771,6780,5,6832,6845,5,6847,6862,5,6916,6916,7,6965,6965,5,6971,6971,7,6973,6977,7,6979,6980,7,7040,7041,5,7073,7073,7,7078,7079,7,7082,7082,7,7142,7142,5,7144,7145,5,7149,7149,5,7151,7153,5,7204,7211,7,7220,7221,7,7376,7378,5,7393,7393,7,7405,7405,5,7415,7415,7,7616,7679,5,8204,8204,5,8206,8207,4,8233,8233,4,8252,8252,14,8288,8292,4,8294,8303,4,8413,8416,5,8418,8420,5,8482,8482,14,8596,8601,14,8986,8987,14,9096,9096,14,9193,9196,14,9199,9199,14,9201,9202,14,9208,9210,14,9642,9643,14,9664,9664,14,9728,9729,14,9732,9732,14,9735,9741,14,9743,9744,14,9746,9746,14,9750,9751,14,9753,9756,14,9758,9759,14,9761,9761,14,9764,9765,14,9767,9769,14,9771,9773,14,9775,9775,14,9784,9785,14,9787,9791,14,9793,9793,14,9795,9799,14,9812,9822,14,9824,9824,14,9827,9827,14,9829,9830,14,9832,9832,14,9851,9851,14,9854,9854,14,9856,9861,14,9874,9874,14,9876,9876,14,9878,9879,14,9881,9881,14,9883,9884,14,9888,9889,14,9895,9895,14,9898,9899,14,9904,9905,14,9917,9918,14,9924,9925,14,9928,9928,14,9934,9934,14,9936,9936,14,9938,9938,14,9940,9940,14,9961,9961,14,9963,9967,14,9970,9971,14,9973,9973,14,9975,9977,14,9979,9980,14,9982,9985,14,9987,9988,14,9992,9996,14,9998,9998,14,10000,10001,14,10004,10004,14,10013,10013,14,10024,10024,14,10052,10052,14,10060,10060,14,10067,10069,14,10083,10083,14,10085,10087,14,10145,10145,14,10175,10175,14,11013,11015,14,11088,11088,14,11503,11505,5,11744,11775,5,12334,12335,5,12349,12349,14,12951,12951,14,42607,42607,5,42612,42621,5,42736,42737,5,43014,43014,5,43043,43044,7,43047,43047,7,43136,43137,7,43204,43205,5,43263,43263,5,43335,43345,5,43360,43388,8,43395,43395,7,43444,43445,7,43450,43451,7,43454,43456,7,43561,43566,5,43569,43570,5,43573,43574,5,43596,43596,5,43644,43644,5,43698,43700,5,43710,43711,5,43755,43755,7,43758,43759,7,43766,43766,5,44005,44005,5,44008,44008,5,44012,44012,7,44032,44032,11,44060,44060,11,44088,44088,11,44116,44116,11,44144,44144,11,44172,44172,11,44200,44200,11,44228,44228,11,44256,44256,11,44284,44284,11,44312,44312,11,44340,44340,11,44368,44368,11,44396,44396,11,44424,44424,11,44452,44452,11,44480,44480,11,44508,44508,11,44536,44536,11,44564,44564,11,44592,44592,11,44620,44620,11,44648,44648,11,44676,44676,11,44704,44704,11,44732,44732,11,44760,44760,11,44788,44788,11,44816,44816,11,44844,44844,11,44872,44872,11,44900,44900,11,44928,44928,11,44956,44956,11,44984,44984,11,45012,45012,11,45040,45040,11,45068,45068,11,45096,45096,11,45124,45124,11,45152,45152,11,45180,45180,11,45208,45208,11,45236,45236,11,45264,45264,11,45292,45292,11,45320,45320,11,45348,45348,11,45376,45376,11,45404,45404,11,45432,45432,11,45460,45460,11,45488,45488,11,45516,45516,11,45544,45544,11,45572,45572,11,45600,45600,11,45628,45628,11,45656,45656,11,45684,45684,11,45712,45712,11,45740,45740,11,45768,45768,11,45796,45796,11,45824,45824,11,45852,45852,11,45880,45880,11,45908,45908,11,45936,45936,11,45964,45964,11,45992,45992,11,46020,46020,11,46048,46048,11,46076,46076,11,46104,46104,11,46132,46132,11,46160,46160,11,46188,46188,11,46216,46216,11,46244,46244,11,46272,46272,11,46300,46300,11,46328,46328,11,46356,46356,11,46384,46384,11,46412,46412,11,46440,46440,11,46468,46468,11,46496,46496,11,46524,46524,11,46552,46552,11,46580,46580,11,46608,46608,11,46636,46636,11,46664,46664,11,46692,46692,11,46720,46720,11,46748,46748,11,46776,46776,11,46804,46804,11,46832,46832,11,46860,46860,11,46888,46888,11,46916,46916,11,46944,46944,11,46972,46972,11,47000,47000,11,47028,47028,11,47056,47056,11,47084,47084,11,47112,47112,11,47140,47140,11,47168,47168,11,47196,47196,11,47224,47224,11,47252,47252,11,47280,47280,11,47308,47308,11,47336,47336,11,47364,47364,11,47392,47392,11,47420,47420,11,47448,47448,11,47476,47476,11,47504,47504,11,47532,47532,11,47560,47560,11,47588,47588,11,47616,47616,11,47644,47644,11,47672,47672,11,47700,47700,11,47728,47728,11,47756,47756,11,47784,47784,11,47812,47812,11,47840,47840,11,47868,47868,11,47896,47896,11,47924,47924,11,47952,47952,11,47980,47980,11,48008,48008,11,48036,48036,11,48064,48064,11,48092,48092,11,48120,48120,11,48148,48148,11,48176,48176,11,48204,48204,11,48232,48232,11,48260,48260,11,48288,48288,11,48316,48316,11,48344,48344,11,48372,48372,11,48400,48400,11,48428,48428,11,48456,48456,11,48484,48484,11,48512,48512,11,48540,48540,11,48568,48568,11,48596,48596,11,48624,48624,11,48652,48652,11,48680,48680,11,48708,48708,11,48736,48736,11,48764,48764,11,48792,48792,11,48820,48820,11,48848,48848,11,48876,48876,11,48904,48904,11,48932,48932,11,48960,48960,11,48988,48988,11,49016,49016,11,49044,49044,11,49072,49072,11,49100,49100,11,49128,49128,11,49156,49156,11,49184,49184,11,49212,49212,11,49240,49240,11,49268,49268,11,49296,49296,11,49324,49324,11,49352,49352,11,49380,49380,11,49408,49408,11,49436,49436,11,49464,49464,11,49492,49492,11,49520,49520,11,49548,49548,11,49576,49576,11,49604,49604,11,49632,49632,11,49660,49660,11,49688,49688,11,49716,49716,11,49744,49744,11,49772,49772,11,49800,49800,11,49828,49828,11,49856,49856,11,49884,49884,11,49912,49912,11,49940,49940,11,49968,49968,11,49996,49996,11,50024,50024,11,50052,50052,11,50080,50080,11,50108,50108,11,50136,50136,11,50164,50164,11,50192,50192,11,50220,50220,11,50248,50248,11,50276,50276,11,50304,50304,11,50332,50332,11,50360,50360,11,50388,50388,11,50416,50416,11,50444,50444,11,50472,50472,11,50500,50500,11,50528,50528,11,50556,50556,11,50584,50584,11,50612,50612,11,50640,50640,11,50668,50668,11,50696,50696,11,50724,50724,11,50752,50752,11,50780,50780,11,50808,50808,11,50836,50836,11,50864,50864,11,50892,50892,11,50920,50920,11,50948,50948,11,50976,50976,11,51004,51004,11,51032,51032,11,51060,51060,11,51088,51088,11,51116,51116,11,51144,51144,11,51172,51172,11,51200,51200,11,51228,51228,11,51256,51256,11,51284,51284,11,51312,51312,11,51340,51340,11,51368,51368,11,51396,51396,11,51424,51424,11,51452,51452,11,51480,51480,11,51508,51508,11,51536,51536,11,51564,51564,11,51592,51592,11,51620,51620,11,51648,51648,11,51676,51676,11,51704,51704,11,51732,51732,11,51760,51760,11,51788,51788,11,51816,51816,11,51844,51844,11,51872,51872,11,51900,51900,11,51928,51928,11,51956,51956,11,51984,51984,11,52012,52012,11,52040,52040,11,52068,52068,11,52096,52096,11,52124,52124,11,52152,52152,11,52180,52180,11,52208,52208,11,52236,52236,11,52264,52264,11,52292,52292,11,52320,52320,11,52348,52348,11,52376,52376,11,52404,52404,11,52432,52432,11,52460,52460,11,52488,52488,11,52516,52516,11,52544,52544,11,52572,52572,11,52600,52600,11,52628,52628,11,52656,52656,11,52684,52684,11,52712,52712,11,52740,52740,11,52768,52768,11,52796,52796,11,52824,52824,11,52852,52852,11,52880,52880,11,52908,52908,11,52936,52936,11,52964,52964,11,52992,52992,11,53020,53020,11,53048,53048,11,53076,53076,11,53104,53104,11,53132,53132,11,53160,53160,11,53188,53188,11,53216,53216,11,53244,53244,11,53272,53272,11,53300,53300,11,53328,53328,11,53356,53356,11,53384,53384,11,53412,53412,11,53440,53440,11,53468,53468,11,53496,53496,11,53524,53524,11,53552,53552,11,53580,53580,11,53608,53608,11,53636,53636,11,53664,53664,11,53692,53692,11,53720,53720,11,53748,53748,11,53776,53776,11,53804,53804,11,53832,53832,11,53860,53860,11,53888,53888,11,53916,53916,11,53944,53944,11,53972,53972,11,54000,54000,11,54028,54028,11,54056,54056,11,54084,54084,11,54112,54112,11,54140,54140,11,54168,54168,11,54196,54196,11,54224,54224,11,54252,54252,11,54280,54280,11,54308,54308,11,54336,54336,11,54364,54364,11,54392,54392,11,54420,54420,11,54448,54448,11,54476,54476,11,54504,54504,11,54532,54532,11,54560,54560,11,54588,54588,11,54616,54616,11,54644,54644,11,54672,54672,11,54700,54700,11,54728,54728,11,54756,54756,11,54784,54784,11,54812,54812,11,54840,54840,11,54868,54868,11,54896,54896,11,54924,54924,11,54952,54952,11,54980,54980,11,55008,55008,11,55036,55036,11,55064,55064,11,55092,55092,11,55120,55120,11,55148,55148,11,55176,55176,11,55216,55238,9,64286,64286,5,65056,65071,5,65438,65439,5,65529,65531,4,66272,66272,5,68097,68099,5,68108,68111,5,68159,68159,5,68900,68903,5,69446,69456,5,69632,69632,7,69634,69634,7,69744,69744,5,69759,69761,5,69808,69810,7,69815,69816,7,69821,69821,1,69837,69837,1,69927,69931,5,69933,69940,5,70003,70003,5,70018,70018,7,70070,70078,5,70082,70083,1,70094,70094,7,70188,70190,7,70194,70195,7,70197,70197,7,70206,70206,5,70368,70370,7,70400,70401,5,70459,70460,5,70463,70463,7,70465,70468,7,70475,70477,7,70498,70499,7,70512,70516,5,70712,70719,5,70722,70724,5,70726,70726,5,70832,70832,5,70835,70840,5,70842,70842,5,70845,70845,5,70847,70848,5,70850,70851,5,71088,71089,7,71096,71099,7,71102,71102,7,71132,71133,5,71219,71226,5,71229,71229,5,71231,71232,5,71340,71340,7,71342,71343,7,71350,71350,7,71453,71455,5,71462,71462,7,71724,71726,7,71736,71736,7,71984,71984,5,71991,71992,7,71997,71997,7,71999,71999,1,72001,72001,1,72003,72003,5,72148,72151,5,72156,72159,7,72164,72164,7,72243,72248,5,72250,72250,1,72263,72263,5,72279,72280,7,72324,72329,1,72343,72343,7,72751,72751,7,72760,72765,5,72767,72767,5,72873,72873,7,72881,72881,7,72884,72884,7,73009,73014,5,73020,73021,5,73030,73030,1,73098,73102,7,73107,73108,7,73110,73110,7,73459,73460,5,78896,78904,4,92976,92982,5,94033,94087,7,94180,94180,5,113821,113822,5,118528,118573,5,119141,119141,5,119143,119145,5,119150,119154,5,119163,119170,5,119210,119213,5,121344,121398,5,121461,121461,5,121499,121503,5,122880,122886,5,122907,122913,5,122918,122922,5,123566,123566,5,125136,125142,5,126976,126979,14,126981,127182,14,127184,127231,14,127279,127279,14,127344,127345,14,127374,127374,14,127405,127461,14,127489,127490,14,127514,127514,14,127538,127546,14,127561,127567,14,127570,127743,14,127757,127758,14,127760,127760,14,127762,127762,14,127766,127768,14,127770,127770,14,127772,127772,14,127775,127776,14,127778,127779,14,127789,127791,14,127794,127795,14,127798,127798,14,127819,127819,14,127824,127824,14,127868,127868,14,127870,127871,14,127892,127893,14,127896,127896,14,127900,127901,14,127904,127940,14,127942,127942,14,127944,127944,14,127946,127946,14,127951,127955,14,127968,127971,14,127973,127984,14,127987,127987,14,127989,127989,14,127991,127991,14,127995,127999,5,128008,128008,14,128012,128014,14,128017,128018,14,128020,128020,14,128022,128022,14,128042,128042,14,128063,128063,14,128065,128065,14,128101,128101,14,128108,128109,14,128173,128173,14,128182,128183,14,128236,128237,14,128239,128239,14,128245,128245,14,128248,128248,14,128253,128253,14,128255,128258,14,128260,128263,14,128265,128265,14,128277,128277,14,128300,128301,14,128326,128328,14,128331,128334,14,128336,128347,14,128360,128366,14,128369,128370,14,128378,128378,14,128391,128391,14,128394,128397,14,128400,128400,14,128405,128406,14,128420,128420,14,128422,128423,14,128425,128432,14,128435,128443,14,128445,128449,14,128453,128464,14,128468,128475,14,128479,128480,14,128482,128482,14,128484,128487,14,128489,128494,14,128496,128498,14,128500,128505,14,128507,128511,14,128513,128518,14,128521,128525,14,128527,128527,14,128529,128529,14,128533,128533,14,128535,128535,14,128537,128537,14]'); } //#endregion diff --git a/src/vs/base/node/ps.ts b/src/vs/base/node/ps.ts index 786da64c247..0a4d82d0ae4 100644 --- a/src/vs/base/node/ps.ts +++ b/src/vs/base/node/ps.ts @@ -48,18 +48,15 @@ export function listProcesses(rootPid: number): Promise { 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 { 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]; } diff --git a/src/vs/base/parts/ipc/common/ipc.net.ts b/src/vs/base/parts/ipc/common/ipc.net.ts index f72112c1522..6229c073c7a 100644 --- a/src/vs/base/parts/ipc/common/ipc.net.ts +++ b/src/vs/base/parts/ipc/common/ipc.net.ts @@ -7,7 +7,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 { @@ -484,7 +483,7 @@ export class BufferedEmitter { // 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()); + queueMicrotask(() => this._deliverMessages()); }, onLastListenerRemove: () => { this._hasListeners = false; diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index 6b84d0aa6c3..9fbbf2ec5f6 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -490,6 +490,11 @@ class QuickPick extends QuickInput implements IQuickPi if (this._value !== value) { this._value = value || ''; this.update(); + // TODO: Remove this duplicate code and have the updating of the input box handle this. + const didFilter = this.ui.list.filter(this.filterValue(this.ui.inputBox.value)); + if (didFilter) { + this.trySelectFirst(); + } this.onDidChangeValueEmitter.fire(this._value); } } @@ -1056,7 +1061,6 @@ class QuickPick extends QuickInput implements IQuickPi } class InputBox extends QuickInput implements IInputBox { - private _value = ''; private _valueSelection: Readonly<[number, number]> | undefined; private valueSelectionUpdated = true; private _placeholder: string | undefined; @@ -1066,12 +1070,11 @@ class InputBox extends QuickInput implements IInputBox { private readonly onDidAcceptEmitter = this._register(new Emitter()); get value() { - return this._value; + return this.ui.inputBox.value; } set value(value: string) { - this._value = value || ''; - this.update(); + this.ui.inputBox.value = value ?? ''; } set valueSelection(valueSelection: Readonly<[number, number]>) { @@ -1118,10 +1121,6 @@ class InputBox extends QuickInput implements IInputBox { if (!this.visible) { this.visibleDisposables.add( this.ui.inputBox.onDidChange(value => { - if (value === this.value) { - return; - } - this._value = value; this.onDidValueChangeEmitter.fire(value); })); this.visibleDisposables.add(this.ui.onDidAccept(() => this.onDidAcceptEmitter.fire())); @@ -1141,9 +1140,6 @@ class InputBox extends QuickInput implements IInputBox { }; this.ui.setVisibilities(visibilities); super.update(); - if (this.ui.inputBox.value !== this.value) { - this.ui.inputBox.value = this.value; - } if (this.valueSelectionUpdated) { this.valueSelectionUpdated = false; this.ui.inputBox.select(this._valueSelection && { start: this._valueSelection[0], end: this._valueSelection[1] }); @@ -1687,7 +1683,7 @@ export class QuickInputController extends Disposable { while (currentElement && !currentElement.offsetParent) { currentElement = withNullAsUndefined(currentElement.parentElement); } - if (currentElement?.offsetParent && !currentElement.parentElement?.classList.contains('monaco-editor')) { + if (currentElement?.offsetParent) { currentElement.focus(); this.previousFocusElement = undefined; } else { diff --git a/src/vs/base/test/browser/markdownRenderer.test.ts b/src/vs/base/test/browser/markdownRenderer.test.ts index befafc099b4..fc154b0f8d3 100644 --- a/src/vs/base/test/browser/markdownRenderer.test.ts +++ b/src/vs/base/test/browser/markdownRenderer.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import { renderMarkdown, renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { parse } from 'vs/base/common/marshalling'; +import { isWeb } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; function strToNode(str: string): HTMLElement { @@ -56,6 +57,14 @@ suite('MarkdownRenderer', () => { const result: HTMLElement = renderMarkdown({ value: `![image](http://example.com/cat.gif|height=200,width=100 'caption')` }).element; assertNodeEquals(result, `

image

`); }); + + test('image with file uri should render as same origin uri', () => { + if (isWeb) { + return; + } + const result: HTMLElement = renderMarkdown({ value: `![image](file:///images/cat.gif)` }).element; + assertNodeEquals(result, '

image

'); + }); }); suite('Code block renderer', () => { @@ -139,7 +148,7 @@ suite('MarkdownRenderer', () => { mds.appendMarkdown(`[$(zap)-link](#link)`); let result: HTMLElement = renderMarkdown(mds).element; - assert.strictEqual(result.innerHTML, `

-link

`); + assert.strictEqual(result.innerHTML, `

-link

`); }); test('render icon in table', () => { @@ -159,7 +168,7 @@ suite('MarkdownRenderer', () => { --link +-link `); @@ -251,5 +260,29 @@ suite('MarkdownRenderer', () => { const result = renderMarkdown(mds).element; assert.strictEqual(result.innerHTML, `

a<b>b</b>c

`); }); + + test('Should render html images', () => { + if (isWeb) { + return; + } + + const mds = new MarkdownString(undefined, { supportHtml: true }); + mds.appendMarkdown(``); + + const result = renderMarkdown(mds).element; + assert.strictEqual(result.innerHTML, ``); + }); + + test('Should render html images with file uri as same origin uri', () => { + if (isWeb) { + return; + } + + const mds = new MarkdownString(undefined, { supportHtml: true }); + mds.appendMarkdown(``); + + const result = renderMarkdown(mds).element; + assert.strictEqual(result.innerHTML, ``); + }); }); }); diff --git a/src/vs/base/test/node/keytar.test.ts b/src/vs/base/test/node/keytar.test.ts deleted file mode 100644 index 4e187264b29..00000000000 --- a/src/vs/base/test/node/keytar.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import { isLinux } from 'vs/base/common/platform'; - -suite('Keytar', () => { - - (isLinux ? test.skip : test)('loads and is functional', async () => { // TODO@RMacfarlane test seems to fail on Linux (Error: Unknown or unsupported transport 'disabled' for address 'disabled:') - const keytar = await import('keytar'); - const name = `VSCode Test ${Math.floor(Math.random() * 1e9)}`; - try { - await keytar.setPassword(name, 'foo', 'bar'); - assert.strictEqual(await keytar.findPassword(name), 'bar'); - assert.strictEqual((await keytar.findCredentials(name)).length, 1); - assert.strictEqual(await keytar.getPassword(name, 'foo'), 'bar'); - await keytar.deletePassword(name, 'foo'); - assert.strictEqual(await keytar.getPassword(name, 'foo'), null); - } catch (err) { - // try to clean up - try { - await keytar.deletePassword(name, 'foo'); - } finally { - // eslint-disable-next-line no-unsafe-finally - throw err; - } - } - }); -}); diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts index 553304dc4cc..56eb3683002 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts @@ -17,6 +17,9 @@ export class StorageDataCleaner extends Disposable { // Workspace/Folder storage names are MD5 hashes (128bits / 4 due to hex presentation) private static readonly NON_EMPTY_WORKSPACE_ID_LENGTH = 128 / 4; + // Reserved empty window workspace storage name when in extension development + private static readonly EXTENSION_DEV_EMPTY_WINDOW_ID = 'ext-dev'; + constructor( private readonly backupWorkspacesPath: string, @INativeEnvironmentService private readonly environmentService: INativeEnvironmentService, @@ -42,18 +45,20 @@ export class StorageDataCleaner extends Disposable { const workspaces = JSON.parse(contents) as IBackupWorkspacesFormat; const emptyWorkspaces = workspaces.emptyWorkspaceInfos.map(emptyWorkspace => emptyWorkspace.backupFolder); - // Read all workspace storage folders that exist - const storageFolders = await Promises.readdir(this.environmentService.workspaceStorageHome.fsPath); - await Promise.all(storageFolders.map(async storageFolder => { - if (storageFolder.length === StorageDataCleaner.NON_EMPTY_WORKSPACE_ID_LENGTH) { + // Read all workspace storage folders that exist & cleanup unused + const workspaceStorageFolders = await Promises.readdir(this.environmentService.workspaceStorageHome.fsPath); + await Promise.all(workspaceStorageFolders.map(async workspaceStorageFolder => { + if ( + workspaceStorageFolder.length === StorageDataCleaner.NON_EMPTY_WORKSPACE_ID_LENGTH || // keep non-empty workspaces + workspaceStorageFolder === StorageDataCleaner.EXTENSION_DEV_EMPTY_WINDOW_ID || // keep empty extension dev workspaces + emptyWorkspaces.indexOf(workspaceStorageFolder) >= 0 // keep empty workspaces that are in use + ) { return; } - if (emptyWorkspaces.indexOf(storageFolder) === -1) { - this.logService.trace(`[storage cleanup]: Deleting storage folder ${storageFolder}.`); + this.logService.trace(`[storage cleanup]: Deleting workspace storage folder ${workspaceStorageFolder}.`); - await Promises.rm(join(this.environmentService.workspaceStorageHome.fsPath, storageFolder)); - } + await Promises.rm(join(this.environmentService.workspaceStorageHome.fsPath, workspaceStorageFolder)); })); } catch (error) { onUnexpectedError(error); diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 80d479c2284..e3077d66510 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -84,8 +84,6 @@ import { UserDataSyncChannel } from 'vs/platform/userDataSync/common/userDataSyn import { UserDataSyncStoreManagementService, UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { UserDataAutoSyncService } from 'vs/platform/userDataSync/electron-sandbox/userDataAutoSyncService'; import { ActiveWindowManager } from 'vs/platform/windows/node/windowTracker'; -import { IExtensionHostStarter, ipcExtensionHostStarterChannelName } from 'vs/platform/extensions/common/extensionHostStarter'; -import { ExtensionHostStarter } from 'vs/platform/extensions/node/extensionHostStarter'; import { ISignService } from 'vs/platform/sign/common/sign'; import { SignService } from 'vs/platform/sign/node/signService'; import { ISharedTunnelsService } from 'vs/platform/remote/common/tunnel'; @@ -337,9 +335,6 @@ class SharedProcessMain extends Disposable { // Terminal services.set(ILocalPtyService, this._register(ptyHostService)); - // Extension Host - services.set(IExtensionHostStarter, this._register(new ExtensionHostStarter(logService))); - // Signing services.set(ISignService, new SyncDescriptor(SignService)); @@ -398,10 +393,6 @@ class SharedProcessMain extends Disposable { const localPtyChannel = ProxyChannel.fromService(localPtyService); this.server.registerChannel(TerminalIpcChannels.LocalPty, localPtyChannel); - // Extension Host - const extensionHostStarterChannel = ProxyChannel.fromService(accessor.get(IExtensionHostStarter)); - this.server.registerChannel(ipcExtensionHostStarterChannelName, extensionHostStarterChannel); - // Tunnel const sharedProcessTunnelChannel = ProxyChannel.fromService(accessor.get(ISharedProcessTunnelService)); this.server.registerChannel(ipcSharedProcessTunnelChannelName, sharedProcessTunnelChannel); diff --git a/src/vs/code/electron-sandbox/processExplorer/media/processExplorer.css b/src/vs/code/electron-sandbox/processExplorer/media/processExplorer.css index 0129b721f77..3baf48ce1af 100644 --- a/src/vs/code/electron-sandbox/processExplorer/media/processExplorer.css +++ b/src/vs/code/electron-sandbox/processExplorer/media/processExplorer.css @@ -38,7 +38,7 @@ body { } .cpu { - width: 45px; + width: 60px; } .pid { diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts index f447d8e8645..5f2040266cf 100644 --- a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts @@ -115,7 +115,7 @@ class ProcessHeaderTreeRenderer implements ITreeRenderer, 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 { - const row = document.getElementById(pid.toString()); + const row = document.getElementById(`pid-${pid}`); if (row) { this.nativeHostService.writeClipboardText(row.innerText); } diff --git a/src/vs/editor/browser/controller/textAreaState.ts b/src/vs/editor/browser/controller/textAreaState.ts index 4c240bfd378..cf8846dbc02 100644 --- a/src/vs/editor/browser/controller/textAreaState.ts +++ b/src/vs/editor/browser/controller/textAreaState.ts @@ -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/ diff --git a/src/vs/editor/browser/view/domLineBreaksComputer.ts b/src/vs/editor/browser/view/domLineBreaksComputer.ts index 68b9aab8b09..fbc84e86d86 100644 --- a/src/vs/editor/browser/view/domLineBreaksComputer.ts +++ b/src/vs/editor/browser/view/domLineBreaksComputer.ts @@ -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 { diff --git a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts index 8560d1680a6..6e2b684002e 100644 --- a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts +++ b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts @@ -230,6 +230,7 @@ class Widget { this._renderData = null; this.domNode.setPosition((this._fixedOverflowWidgets && this.allowEditorOverflow) ? 'fixed' : 'absolute'); + this.domNode.setDisplay('none'); this.domNode.setVisibility('hidden'); this.domNode.setAttribute('widgetId', this.id); this.domNode.setMaxWidth(this._maxWidth); @@ -274,6 +275,15 @@ class Widget { public setPosition(range: IRange | null, preference: ContentWidgetPositionPreference[] | null): void { this._setPosition(range); this._preference = preference; + if (this._viewRange && this._preference && this._preference.length > 0) { + // this content widget would like to be visible if possible + // we change it from `display:none` to `display:block` even if it + // might be outside the viewport such that we can measure its size + // in `prepareRender` + this.domNode.setDisplay('block'); + } else { + this.domNode.setDisplay('none'); + } this._cachedDomNodeClientWidth = -1; this._cachedDomNodeClientHeight = -1; } @@ -435,6 +445,10 @@ class Widget { } private _prepareRenderWidget(ctx: RenderingContext): IRenderData | null { + if (!this._preference || this._preference.length === 0) { + return null; + } + const [topLeft, bottomLeft] = this._getTopAndBottomLeft(ctx); if (!topLeft || !bottomLeft) { return null; @@ -464,36 +478,35 @@ class Widget { } // Do two passes, first for perfect fit, second picks first option - if (this._preference) { - for (let pass = 1; pass <= 2; pass++) { - for (const pref of this._preference) { - // placement - if (pref === ContentWidgetPositionPreference.ABOVE) { - if (!placement) { - // Widget outside of viewport - return null; - } - if (pass === 2 || placement.fitsAbove) { - return { coordinate: new Coordinate(placement.aboveTop, placement.aboveLeft), position: ContentWidgetPositionPreference.ABOVE }; - } - } else if (pref === ContentWidgetPositionPreference.BELOW) { - if (!placement) { - // Widget outside of viewport - return null; - } - if (pass === 2 || placement.fitsBelow) { - return { coordinate: new Coordinate(placement.belowTop, placement.belowLeft), position: ContentWidgetPositionPreference.BELOW }; - } + for (let pass = 1; pass <= 2; pass++) { + for (const pref of this._preference) { + // placement + if (pref === ContentWidgetPositionPreference.ABOVE) { + if (!placement) { + // Widget outside of viewport + return null; + } + if (pass === 2 || placement.fitsAbove) { + return { coordinate: new Coordinate(placement.aboveTop, placement.aboveLeft), position: ContentWidgetPositionPreference.ABOVE }; + } + } else if (pref === ContentWidgetPositionPreference.BELOW) { + if (!placement) { + // Widget outside of viewport + return null; + } + if (pass === 2 || placement.fitsBelow) { + return { coordinate: new Coordinate(placement.belowTop, placement.belowLeft), position: ContentWidgetPositionPreference.BELOW }; + } + } else { + if (this.allowEditorOverflow) { + return { coordinate: this._prepareRenderWidgetAtExactPositionOverflowing(topLeft), position: ContentWidgetPositionPreference.EXACT }; } else { - if (this.allowEditorOverflow) { - return { coordinate: this._prepareRenderWidgetAtExactPositionOverflowing(topLeft), position: ContentWidgetPositionPreference.EXACT }; - } else { - return { coordinate: topLeft, position: ContentWidgetPositionPreference.EXACT }; - } + return { coordinate: topLeft, position: ContentWidgetPositionPreference.EXACT }; } } } } + return null; } diff --git a/src/vs/editor/browser/viewParts/lines/viewLine.ts b/src/vs/editor/browser/viewParts/lines/viewLine.ts index fcc405103f6..9148c986b73 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLine.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLine.ts @@ -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) { diff --git a/src/vs/editor/common/controller/cursorTypeOperations.ts b/src/vs/editor/common/controller/cursorTypeOperations.ts index 09dbf3671cf..4f8a560a404 100644 --- a/src/vs/editor/common/controller/cursorTypeOperations.ts +++ b/src/vs/editor/common/controller/cursorTypeOperations.ts @@ -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); } } diff --git a/src/vs/editor/common/core/token.ts b/src/vs/editor/common/core/token.ts index 9d7f3560197..083d7c29d15 100644 --- a/src/vs/editor/common/core/token.ts +++ b/src/vs/editor/common/core/token.ts @@ -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; } diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index a6905eb8491..33b0669d37e 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -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'; @@ -950,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. diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 53020b43e01..c1763b1af7a 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -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); } diff --git a/src/vs/editor/common/model/textModelTokens.ts b/src/vs/editor/common/model/textModelTokens.ts index 04aa75a2096..cabbec32642 100644 --- a/src/vs/editor/common/model/textModelTokens.ts +++ b/src/vs/editor/common/model/textModelTokens.ts @@ -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; diff --git a/src/vs/editor/common/modes/languageConfiguration.ts b/src/vs/editor/common/modes/languageConfiguration.ts index c911ae318d0..3c8a5c22b45 100644 --- a/src/vs/editor/common/modes/languageConfiguration.ts +++ b/src/vs/editor/common/modes/languageConfiguration.ts @@ -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 & 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; + } } /** diff --git a/src/vs/editor/common/modes/languageConfigurationRegistry.ts b/src/vs/editor/common/modes/languageConfigurationRegistry.ts index b1b73e3d713..704e51f7866 100644 --- a/src/vs/editor/common/modes/languageConfigurationRegistry.ts +++ b/src/vs/editor/common/modes/languageConfigurationRegistry.ts @@ -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 { diff --git a/src/vs/editor/common/modes/supports/characterPair.ts b/src/vs/editor/common/modes/supports/characterPair.ts index e1f8d13dcd8..61ff9873862 100644 --- a/src/vs/editor/common/modes/supports/characterPair.ts +++ b/src/vs/editor/common/modes/supports/characterPair.ts @@ -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; } diff --git a/src/vs/editor/common/modes/supports/electricCharacter.ts b/src/vs/editor/common/modes/supports/electricCharacter.ts index ead8e6da3a6..1d3ba8ed9ed 100644 --- a/src/vs/editor/common/modes/supports/electricCharacter.ts +++ b/src/vs/editor/common/modes/supports/electricCharacter.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { distinct } from 'vs/base/common/arrays'; import { ScopedLineTokens, ignoreBracketsInToken } from 'vs/editor/common/modes/supports'; import { BracketsUtils, RichEditBrackets } from 'vs/editor/common/modes/supports/richEditBrackets'; @@ -36,12 +37,7 @@ export class BracketElectricCharacterSupport { } } - // Filter duplicate entries - result = result.filter((item, pos, array) => { - return array.indexOf(item) === pos; - }); - - return result; + return distinct(result); } public onElectricCharacter(character: string, context: ScopedLineTokens, column: number): IElectricAction | null { diff --git a/src/vs/editor/common/view/overviewZoneManager.ts b/src/vs/editor/common/view/overviewZoneManager.ts index 2397cb12722..5952be0122d 100644 --- a/src/vs/editor/common/view/overviewZoneManager.ts +++ b/src/vs/editor/common/view/overviewZoneManager.ts @@ -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); diff --git a/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts b/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts index 4c1cd5c4ad9..3479b1739da 100644 --- a/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts +++ b/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts @@ -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]; diff --git a/src/vs/editor/common/viewModel/prefixSumComputer.ts b/src/vs/editor/common/viewModel/prefixSumComputer.ts index 47ac2a2c65b..7c6eba5854e 100644 --- a/src/vs/editor/common/viewModel/prefixSumComputer.ts +++ b/src/vs/editor/common/viewModel/prefixSumComputer.ts @@ -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(); diff --git a/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts b/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts index 2d925dcb22a..b4dd20023c7 100644 --- a/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts @@ -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(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 = 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(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 = 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 }); - }); }); diff --git a/src/vs/editor/contrib/multicursor/multicursor.ts b/src/vs/editor/contrib/multicursor/multicursor.ts index 116308fbd83..9eebf15826b 100644 --- a/src/vs/editor/contrib/multicursor/multicursor.ts +++ b/src/vs/editor/contrib/multicursor/multicursor.ts @@ -817,34 +817,33 @@ export class CompatChangeAll extends MultiCursorSelectionControllerAction { } class SelectionHighlighterState { - public readonly searchText: string; - public readonly matchCase: boolean; - public readonly wordSeparators: string | null; - public readonly modelVersionId: number; + private readonly _modelVersionId: number = this._model.getVersionId(); + private _cachedFindMatches: Range[] | null = null; - constructor(searchText: string, matchCase: boolean, wordSeparators: string | null, modelVersionId: number) { - this.searchText = searchText; - this.matchCase = matchCase; - this.wordSeparators = wordSeparators; - this.modelVersionId = modelVersionId; + constructor( + private readonly _model: ITextModel, + private readonly _searchText: string, + private readonly _matchCase: boolean, + private readonly _wordSeparators: string | null, + prevState: SelectionHighlighterState | null + ) { + if (prevState + && this._model === prevState._model + && this._searchText === prevState._searchText + && this._matchCase === prevState._matchCase + && this._wordSeparators === prevState._wordSeparators + && this._modelVersionId === prevState._modelVersionId + ) { + this._cachedFindMatches = prevState._cachedFindMatches; + } } - /** - * Everything equals except for `lastWordUnderCursor` - */ - public static softEquals(a: SelectionHighlighterState | null, b: SelectionHighlighterState | null): boolean { - if (!a && !b) { - return true; + public findMatches(): Range[] { + if (this._cachedFindMatches === null) { + this._cachedFindMatches = this._model.findMatches(this._searchText, true, false, this._matchCase, this._wordSeparators, false).map(m => m.range); + this._cachedFindMatches.sort(Range.compareRangesUsingStarts); } - if (!a || !b) { - return false; - } - return ( - a.searchText === b.searchText - && a.matchCase === b.matchCase - && a.wordSeparators === b.wordSeparators - && a.modelVersionId === b.modelVersionId - ); + return this._cachedFindMatches; } } @@ -904,10 +903,10 @@ export class SelectionHighlighter extends Disposable implements IEditorContribut } private _update(): void { - this._setState(SelectionHighlighter._createState(this._isEnabled, this.editor)); + this._setState(SelectionHighlighter._createState(this.state, this._isEnabled, this.editor)); } - private static _createState(isEnabled: boolean, editor: ICodeEditor): SelectionHighlighterState | null { + private static _createState(oldState: SelectionHighlighterState | null, isEnabled: boolean, editor: ICodeEditor): SelectionHighlighterState | null { if (!isEnabled) { return null; } @@ -980,15 +979,11 @@ export class SelectionHighlighter extends Disposable implements IEditorContribut } } - return new SelectionHighlighterState(r.searchText, r.matchCase, r.wholeWord ? editor.getOption(EditorOption.wordSeparators) : null, editor.getModel().getVersionId()); + return new SelectionHighlighterState(editor.getModel(), r.searchText, r.matchCase, r.wholeWord ? editor.getOption(EditorOption.wordSeparators) : null, oldState); } - private _setState(state: SelectionHighlighterState | null): void { - if (SelectionHighlighterState.softEquals(this.state, state)) { - this.state = state; - return; - } - this.state = state; + private _setState(newState: SelectionHighlighterState | null): void { + this.state = newState; if (!this.state) { this.decorations = this.editor.deltaDecorations(this.decorations, []); @@ -1001,20 +996,17 @@ export class SelectionHighlighter extends Disposable implements IEditorContribut const model = this.editor.getModel(); if (model.isTooLargeForTokenization()) { - // the file is too large, so searching word under cursor in the whole document takes is blocking the UI. + // the file is too large, so searching word under cursor in the whole document would be blocking the UI. return; } - const hasFindOccurrences = DocumentHighlightProviderRegistry.has(model) && this.editor.getOption(EditorOption.occurrencesHighlight); + const allMatches = this.state.findMatches(); - let allMatches = model.findMatches(this.state.searchText, true, false, this.state.matchCase, this.state.wordSeparators, false).map(m => m.range); - allMatches.sort(Range.compareRangesUsingStarts); - - let selections = this.editor.getSelections(); + const selections = this.editor.getSelections(); selections.sort(Range.compareRangesUsingStarts); // do not overlap with selection (issue #64 and #512) - let matches: Range[] = []; + const matches: Range[] = []; for (let i = 0, j = 0, len = allMatches.length, lenJ = selections.length; i < len;) { const match = allMatches[i]; @@ -1041,6 +1033,7 @@ export class SelectionHighlighter extends Disposable implements IEditorContribut } } + const hasFindOccurrences = DocumentHighlightProviderRegistry.has(model) && this.editor.getOption(EditorOption.occurrencesHighlight); const decorations = matches.map(r => { return { range: r, diff --git a/src/vs/editor/contrib/parameterHints/test/parameterHintsModel.test.ts b/src/vs/editor/contrib/parameterHints/test/parameterHintsModel.test.ts index f935d89342f..99a1b20d6f1 100644 --- a/src/vs/editor/contrib/parameterHints/test/parameterHintsModel.test.ts +++ b/src/vs/editor/contrib/parameterHints/test/parameterHintsModel.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; +import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { Position } from 'vs/editor/common/core/position'; import { Handler } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; @@ -62,7 +63,10 @@ suite('ParameterHintsModel', () => { return editor; } - test('Provider should get trigger character on type', (done) => { + test('Provider should get trigger character on type', async () => { + let done: () => void; + const donePromise = new Promise(resolve => { done = resolve; }); + const triggerChar = '('; const editor = createMockEditor(''); @@ -80,10 +84,16 @@ suite('ParameterHintsModel', () => { } })); - editor.trigger('keyboard', Handler.Type, { text: triggerChar }); + await runWithFakedTimers({ useFakeTimers: true }, async () => { + editor.trigger('keyboard', Handler.Type, { text: triggerChar }); + await donePromise; + }); }); - test('Provider should be retriggered if already active', (done) => { + test('Provider should be retriggered if already active', async () => { + let done: () => void; + const donePromise = new Promise(resolve => { done = resolve; }); + const triggerChar = '('; const editor = createMockEditor(''); @@ -104,7 +114,7 @@ suite('ParameterHintsModel', () => { assert.strictEqual(context.activeSignatureHelp, undefined); // Retrigger - setTimeout(() => editor.trigger('keyboard', Handler.Type, { text: triggerChar }), 50); + setTimeout(() => editor.trigger('keyboard', Handler.Type, { text: triggerChar }), 0); } else { assert.strictEqual(invokeCount, 2); assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter); @@ -122,10 +132,16 @@ suite('ParameterHintsModel', () => { } })); - editor.trigger('keyboard', Handler.Type, { text: triggerChar }); + await runWithFakedTimers({ useFakeTimers: true }, async () => { + editor.trigger('keyboard', Handler.Type, { text: triggerChar }); + await donePromise; + }); }); - test('Provider should not be retriggered if previous help is canceled first', (done) => { + test('Provider should not be retriggered if previous help is canceled first', async () => { + let done: () => void; + const donePromise = new Promise(resolve => { done = resolve; }); + const triggerChar = '('; const editor = createMockEditor(''); @@ -165,10 +181,16 @@ suite('ParameterHintsModel', () => { } })); - editor.trigger('keyboard', Handler.Type, { text: triggerChar }); + await runWithFakedTimers({ useFakeTimers: true }, () => { + editor.trigger('keyboard', Handler.Type, { text: triggerChar }); + return donePromise; + }); }); - test('Provider should get last trigger character when triggered multiple times and only be invoked once', (done) => { + test('Provider should get last trigger character when triggered multiple times and only be invoked once', async () => { + let done: () => void; + const donePromise = new Promise(resolve => { done = resolve; }); + const editor = createMockEditor(''); disposables.add(new ParameterHintsModel(editor, 5)); @@ -199,16 +221,24 @@ suite('ParameterHintsModel', () => { } })); - editor.trigger('keyboard', Handler.Type, { text: 'a' }); - editor.trigger('keyboard', Handler.Type, { text: 'b' }); - editor.trigger('keyboard', Handler.Type, { text: 'c' }); + await runWithFakedTimers({ useFakeTimers: true }, async () => { + editor.trigger('keyboard', Handler.Type, { text: 'a' }); + editor.trigger('keyboard', Handler.Type, { text: 'b' }); + editor.trigger('keyboard', Handler.Type, { text: 'c' }); + + await donePromise; + }); }); - test('Provider should be retriggered if already active', (done) => { + test('Provider should be retriggered if already active', async () => { + let done: () => void; + const donePromise = new Promise(resolve => { done = resolve; }); + const editor = createMockEditor(''); disposables.add(new ParameterHintsModel(editor, 5)); let invokeCount = 0; + disposables.add(modes.SignatureHelpProviderRegistry.register(mockFileSelector, new class implements modes.SignatureHelpProvider { signatureHelpTriggerCharacters = ['a', 'b']; signatureHelpRetriggerCharacters = []; @@ -239,10 +269,14 @@ suite('ParameterHintsModel', () => { } })); - editor.trigger('keyboard', Handler.Type, { text: 'a' }); + await runWithFakedTimers({ useFakeTimers: true }, () => { + editor.trigger('keyboard', Handler.Type, { text: 'a' }); + return donePromise; + }); }); - test('Should cancel existing request when new request comes in', () => { + test('Should cancel existing request when new request comes in', async () => { + const editor = createMockEditor('abc def'); const hintsModel = new ParameterHintsModel(editor); @@ -287,22 +321,28 @@ suite('ParameterHintsModel', () => { disposables.add(modes.SignatureHelpProviderRegistry.register(mockFileSelector, longRunningProvider)); - hintsModel.trigger({ triggerKind: modes.SignatureHelpTriggerKind.Invoke }, 0); - assert.strictEqual(-1, didRequestCancellationOf); + await runWithFakedTimers({ useFakeTimers: true }, async () => { - return new Promise((resolve, reject) => - hintsModel.onChangedHints(newParamterHints => { - try { - assert.strictEqual(0, didRequestCancellationOf); - assert.strictEqual('1', newParamterHints!.signatures[0].label); - resolve(); - } catch (e) { - reject(e); - } - })); + hintsModel.trigger({ triggerKind: modes.SignatureHelpTriggerKind.Invoke }, 0); + assert.strictEqual(-1, didRequestCancellationOf); + + return new Promise((resolve, reject) => + hintsModel.onChangedHints(newParamterHints => { + try { + assert.strictEqual(0, didRequestCancellationOf); + assert.strictEqual('1', newParamterHints!.signatures[0].label); + resolve(); + } catch (e) { + reject(e); + } + })); + }); }); - test('Provider should be retriggered by retrigger character', (done) => { + test('Provider should be retriggered by retrigger character', async () => { + let done: () => void; + const donePromise = new Promise(resolve => { done = resolve; }); + const triggerChar = 'a'; const retriggerChar = 'b'; @@ -340,11 +380,15 @@ suite('ParameterHintsModel', () => { } })); - // This should not trigger anything - editor.trigger('keyboard', Handler.Type, { text: retriggerChar }); + await runWithFakedTimers({ useFakeTimers: true }, async () => { + // This should not trigger anything + editor.trigger('keyboard', Handler.Type, { text: retriggerChar }); - // But a trigger character should - editor.trigger('keyboard', Handler.Type, { text: triggerChar }); + // But a trigger character should + editor.trigger('keyboard', Handler.Type, { text: triggerChar }); + + return donePromise; + }); }); test('should use first result from multiple providers', async () => { @@ -413,17 +457,19 @@ suite('ParameterHintsModel', () => { } })); - editor.trigger('keyboard', Handler.Type, { text: triggerChar }); + await runWithFakedTimers({ useFakeTimers: true }, async () => { + editor.trigger('keyboard', Handler.Type, { text: triggerChar }); - const firstHint = (await getNextHint(model))!.value; - assert.strictEqual(firstHint.signatures[0].label, firstProviderId); - assert.strictEqual(firstHint.activeSignature, 0); - assert.strictEqual(firstHint.signatures[0].parameters[0].label, paramterLabel); + const firstHint = (await getNextHint(model))!.value; + assert.strictEqual(firstHint.signatures[0].label, firstProviderId); + assert.strictEqual(firstHint.activeSignature, 0); + assert.strictEqual(firstHint.signatures[0].parameters[0].label, paramterLabel); - const secondHint = (await getNextHint(model))!.value; - assert.strictEqual(secondHint.signatures[0].label, secondProviderId); - assert.strictEqual(secondHint.activeSignature, 1); - assert.strictEqual(secondHint.signatures[0].parameters[0].label, paramterLabel); + const secondHint = (await getNextHint(model))!.value; + assert.strictEqual(secondHint.signatures[0].label, secondProviderId); + assert.strictEqual(secondHint.activeSignature, 1); + assert.strictEqual(secondHint.signatures[0].parameters[0].label, paramterLabel); + }); }); test('Quick typing should use the first trigger character', async () => { @@ -457,13 +503,18 @@ suite('ParameterHintsModel', () => { } })); - editor.trigger('keyboard', Handler.Type, { text: triggerCharacter }); - editor.trigger('keyboard', Handler.Type, { text: 'x' }); + await runWithFakedTimers({ useFakeTimers: true }, async () => { + editor.trigger('keyboard', Handler.Type, { text: triggerCharacter }); + editor.trigger('keyboard', Handler.Type, { text: 'x' }); - await getNextHint(model); + await getNextHint(model); + }); }); - test('Retrigger while a pending resolve is still going on should preserve last active signature #96702', (done) => { + test('Retrigger while a pending resolve is still going on should preserve last active signature #96702', async () => { + let done: (r?: any) => void; + const donePromise = new Promise(resolve => { done = resolve; }); + const editor = createMockEditor(''); const model = new ParameterHintsModel(editor, 50); disposables.add(model); @@ -505,10 +556,15 @@ suite('ParameterHintsModel', () => { } })); - editor.trigger('keyboard', Handler.Type, { text: triggerCharacter }); + await runWithFakedTimers({ useFakeTimers: true }, async () => { - getNextHint(model) - .then(() => getNextHint(model)); + editor.trigger('keyboard', Handler.Type, { text: triggerCharacter }); + + await getNextHint(model); + await getNextHint(model); + + await donePromise; + }); }); }); diff --git a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts index 0c82db5f0d8..ddc4b3e90af 100644 --- a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { DeferredPromise } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; import { IMatch } from 'vs/base/common/filters'; @@ -101,22 +102,21 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit return true; } - let symbolProviderRegistryPromiseResolve: (res: boolean) => void; - const symbolProviderRegistryPromise = new Promise(resolve => symbolProviderRegistryPromiseResolve = resolve); + const symbolProviderRegistryPromise = new DeferredPromise(); // Resolve promise when registry knows model const symbolProviderListener = disposables.add(DocumentSymbolProviderRegistry.onDidChange(() => { if (DocumentSymbolProviderRegistry.has(model)) { symbolProviderListener.dispose(); - symbolProviderRegistryPromiseResolve(true); + symbolProviderRegistryPromise.complete(true); } })); // Resolve promise when we get disposed too - disposables.add(toDisposable(() => symbolProviderRegistryPromiseResolve(false))); + disposables.add(toDisposable(() => symbolProviderRegistryPromise.complete(false))); - return symbolProviderRegistryPromise; + return symbolProviderRegistryPromise.p; } private doProvideWithEditorSymbols(context: IQuickAccessTextEditorContext, model: ITextModel, picker: IQuickPick, token: CancellationToken): IDisposable { diff --git a/src/vs/editor/contrib/suggest/suggestController.ts b/src/vs/editor/contrib/suggest/suggestController.ts index 6de2eb0900c..42e2561cef1 100644 --- a/src/vs/editor/contrib/suggest/suggestController.ts +++ b/src/vs/editor/contrib/suggest/suggestController.ts @@ -43,6 +43,9 @@ import { CommitCharacterController } from './suggestCommitCharacters'; import { State, SuggestModel } from './suggestModel'; import { OvertypingCapturer } from './suggestOvertypingCapturer'; import { ISelectedSuggestion, SuggestWidget } from './suggestWidget'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { basename, extname } from 'vs/base/common/resources'; +import { hash } from 'vs/base/common/hash'; // sticky suggest widget which doesn't disappear on focus out and such let _sticky = false; @@ -122,6 +125,7 @@ export class SuggestController implements IEditorContribution { @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ILogService private readonly _logService: ILogService, + @ITelemetryService private readonly _telemetryService: ITelemetryService, ) { this.editor = editor; this.model = _instantiationService.createInstance(SuggestModel, this.editor,); @@ -427,11 +431,37 @@ export class SuggestController implements IEditorContribution { // clear only now - after all tasks are done Promise.all(tasks).finally(() => { + this._reportSuggestionAcceptedTelemetry(model, event); + this.model.clear(); cts.dispose(); }); } + private _telemetryGate: number = 0; + private _reportSuggestionAcceptedTelemetry(model: ITextModel, acceptedSuggestion: ISelectedSuggestion) { + if (this._telemetryGate++ % 100 !== 0) { + return; + } + + type AcceptedSuggestion = { providerId: string; fileExtension: string; languageId: string; basenameHash: string; }; + type AcceptedSuggestionClassification = { + providerId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; }; + basenameHash: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; }; + fileExtension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; }; + languageId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; }; + }; + // _debugDisplayName looks like `vscode.css-language-features(/-:)`, where the last bit is the trigger chars + // normalize it to just the extension ID and lowercase + const providerId = (acceptedSuggestion.item.provider._debugDisplayName ?? 'unknown').split('(', 1)[0].toLowerCase(); + this._telemetryService.publicLog2('suggest.acceptedSuggestion', { + providerId, + basenameHash: hash(basename(model.uri)).toString(16), + languageId: model.getLanguageId(), + fileExtension: extname(model.uri), + }); + } + getOverwriteInfo(item: CompletionItem, toggleMode: boolean): { overwriteBefore: number, overwriteAfter: number } { assertType(this.editor.hasModel()); diff --git a/src/vs/editor/contrib/suggest/suggestWidgetDetails.ts b/src/vs/editor/contrib/suggest/suggestWidgetDetails.ts index b5e9cad6a46..e12163c1035 100644 --- a/src/vs/editor/contrib/suggest/suggestWidgetDetails.ts +++ b/src/vs/editor/contrib/suggest/suggestWidgetDetails.ts @@ -363,7 +363,7 @@ export class SuggestDetailsOverlay implements IOverlayWidget { } placeAtAnchor(anchor: HTMLElement, preferAlignAtTop: boolean) { - const anchorBox = dom.getDomNodePagePosition(anchor); + const anchorBox = anchor.getBoundingClientRect(); this._anchorBox = anchorBox; this._preferAlignAtTop = preferAlignAtTop; this._placeAtAnchor(this._anchorBox, this._userSize ?? this.widget.size, preferAlignAtTop); diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index aa6af2cf6bb..8a1dc5e697c 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -14,16 +14,18 @@ import { TokenizationResult2 } from 'vs/editor/common/core/token'; import { ICommand, ICursorStateComputerData, IEditOperationBuilder } from 'vs/editor/common/editorCommon'; import { EndOfLinePreference, EndOfLineSequence, ITextModel } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; -import { IState, ITokenizationSupport, TokenizationRegistry } from 'vs/editor/common/modes'; +import { IState, ITokenizationSupport, MetadataConsts, StandardTokenType, TokenizationRegistry } from 'vs/editor/common/modes'; import { IndentAction, IndentationRule } from 'vs/editor/common/modes/languageConfiguration'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { NULL_STATE } from 'vs/editor/common/modes/nullMode'; -import { withTestCodeEditor, TestCodeEditorCreationOptions, ITestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; -import { IRelaxedTextModelCreationOptions, createTextModel } from 'vs/editor/test/common/editorTestUtils'; +import { withTestCodeEditor, TestCodeEditorCreationOptions, ITestCodeEditor, createCodeEditorServices } from 'vs/editor/test/browser/testCodeEditor'; +import { IRelaxedTextModelCreationOptions, createTextModel, createTextModel2 } from 'vs/editor/test/common/editorTestUtils'; import { MockMode } from 'vs/editor/test/common/mocks/mockMode'; import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { OutgoingViewModelEventKind } from 'vs/editor/common/viewModel/viewModelEventDispatcher'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { DisposableStore } from 'vs/base/common/lifecycle'; // --------- utils @@ -4810,7 +4812,7 @@ suite('autoClosingPairs', () => { private static readonly _id = 'autoClosingMode'; - constructor() { + constructor(modeService: IModeService | null = null) { super(AutoClosingMode._id); this._register(LanguageConfigurationRegistry.register(this.languageId, { autoClosingPairs: [ @@ -4827,6 +4829,129 @@ suite('autoClosingPairs', () => { docComment: { open: '/**', close: ' */' } } })); + class BaseState implements IState { + constructor( + public readonly parent: State | null = null + ) { } + clone(): IState { return this; } + equals(other: IState): boolean { + if (!(other instanceof BaseState)) { + return false; + } + if (!this.parent && !other.parent) { + return true; + } + if (!this.parent || !other.parent) { + return false; + } + return this.parent.equals(other.parent); + } + } + class StringState implements IState { + constructor( + public readonly char: string, + public readonly parentState: State + ) { } + clone(): IState { return this; } + equals(other: IState): boolean { return other instanceof StringState && this.char === other.char && this.parentState.equals(other.parentState); } + } + class BlockCommentState implements IState { + constructor( + public readonly parentState: State + ) { } + clone(): IState { return this; } + equals(other: IState): boolean { return other instanceof StringState && this.parentState.equals(other.parentState); } + } + type State = BaseState | StringState | BlockCommentState; + + if (modeService) { + const encodedLanguageId = modeService.languageIdCodec.encodeLanguageId(this.languageId); + this._register(TokenizationRegistry.register(this.languageId, { + getInitialState: () => new BaseState(), + tokenize: undefined!, + tokenize2: function (line: string, hasEOL: boolean, _state: IState, offsetDelta: number): TokenizationResult2 { + let state = _state; + const tokens: { length: number; type: StandardTokenType; }[] = []; + const generateToken = (length: number, type: StandardTokenType, newState?: State) => { + if (tokens.length > 0 && tokens[tokens.length - 1].type === type) { + // grow last tokens + tokens[tokens.length - 1].length += length; + } else { + tokens.push({ length, type }); + } + line = line.substring(length); + if (newState) { + state = newState; + } + }; + while (line.length > 0) { + advance(); + } + let result = new Uint32Array(tokens.length * 2); + let startIndex = 0; + for (let i = 0; i < tokens.length; i++) { + result[2 * i] = startIndex; + result[2 * i + 1] = ( + (encodedLanguageId << MetadataConsts.LANGUAGEID_OFFSET) + | (tokens[i].type << MetadataConsts.TOKEN_TYPE_OFFSET) + ); + startIndex += tokens[i].length; + } + return new TokenizationResult2(result, state); + + function advance(): void { + if (state instanceof BaseState) { + const m1 = line.match(/^[^'"`{}/]+/g); + if (m1) { + return generateToken(m1[0].length, StandardTokenType.Other); + } + if (/^['"`]/.test(line)) { + return generateToken(1, StandardTokenType.String, new StringState(line.charAt(0), state)); + } + if (/^{/.test(line)) { + return generateToken(1, StandardTokenType.Other, new BaseState(state)); + } + if (/^}/.test(line)) { + return generateToken(1, StandardTokenType.Other, state.parent || new BaseState()); + } + if (/^\/\//.test(line)) { + return generateToken(line.length, StandardTokenType.Comment, state); + } + if (/^\/\*/.test(line)) { + return generateToken(2, StandardTokenType.Comment, new BlockCommentState(state)); + } + return generateToken(1, StandardTokenType.Other, state); + } else if (state instanceof StringState) { + const m1 = line.match(/^[^\\'"`\$]+/g); + if (m1) { + return generateToken(m1[0].length, StandardTokenType.String); + } + if (/^\\/.test(line)) { + return generateToken(2, StandardTokenType.String); + } + if (line.charAt(0) === state.char) { + return generateToken(1, StandardTokenType.String, state.parentState); + } + if (/^\$\{/.test(line)) { + return generateToken(2, StandardTokenType.Other, new BaseState(state)); + } + return generateToken(1, StandardTokenType.Other, state); + } else if (state instanceof BlockCommentState) { + const m1 = line.match(/^[^*]+/g); + if (m1) { + return generateToken(m1[0].length, StandardTokenType.String); + } + if (/^\*\//.test(line)) { + return generateToken(2, StandardTokenType.Comment, state.parentState); + } + return generateToken(1, StandardTokenType.Other, state); + } else { + throw new Error(`unknown state`); + } + } + } + })); + } } public setAutocloseEnabledSet(chars: string) { @@ -4869,7 +4994,7 @@ suite('autoClosingPairs', () => { return result; } - function assertType(editor: ITestCodeEditor, model: TextModel, viewModel: ViewModel, lineNumber: number, column: number, chr: string, expectedInsert: string, message: string): void { + function assertType(editor: ITestCodeEditor, model: ITextModel, viewModel: ViewModel, lineNumber: number, column: number, chr: string, expectedInsert: string, message: string): void { let lineContent = model.getLineContent(lineNumber); let expected = lineContent.substr(0, column - 1) + expectedInsert + lineContent.substr(column - 1); moveTo(editor, viewModel, lineNumber, column); @@ -4890,6 +5015,25 @@ suite('autoClosingPairs', () => { mode.dispose(); }); + test('issue #132912: quotes should not auto-close if they are closing a string', () => { + const disposables = new DisposableStore(); + const instantiationService = createCodeEditorServices(disposables); + const modeService = instantiationService.invokeFunction((accessor) => accessor.get(IModeService)); + const mode = disposables.add(new AutoClosingMode(modeService)); + withTestCodeEditor( + null, + { + model: disposables.add(createTextModel2(instantiationService, 'const t2 = `something ${t1}', undefined, mode.languageId)) + }, + (editor, viewModel) => { + const model = viewModel.model; + model.forceTokenization(1); + assertType(editor, model, viewModel, 1, 28, '`', '`', `does not auto close \` @ (1, 28)`); + } + ); + disposables.dispose(); + }); + test('open parens: default', () => { let mode = new AutoClosingMode(); usingCursor({ diff --git a/src/vs/editor/test/common/modes/supports/characterPair.test.ts b/src/vs/editor/test/common/modes/supports/characterPair.test.ts index ce1c31ee92d..5d799f478de 100644 --- a/src/vs/editor/test/common/modes/supports/characterPair.test.ts +++ b/src/vs/editor/test/common/modes/supports/characterPair.test.ts @@ -53,78 +53,71 @@ suite('CharacterPairSupport', () => { assert.deepStrictEqual(characaterPairSupport.getSurroundingPairs(), []); }); - function findAutoClosingPair(characterPairSupport: CharacterPairSupport, character: string): StandardAutoClosingPairConditional | undefined { - return characterPairSupport.getAutoClosingPairs().find(autoClosingPair => autoClosingPair.open === character); - } - - function testShouldAutoClose(characterPairSupport: CharacterPairSupport, line: TokenText[], character: string, column: number): boolean { - const autoClosingPair = findAutoClosingPair(characterPairSupport, character); - if (!autoClosingPair) { - return false; - } - return CharacterPairSupport.shouldAutoClosePair(autoClosingPair, createFakeScopedLineTokens(line), column); + function testShouldAutoClose(characterPairSupport: CharacterPairSupport, line: TokenText[], column: number): boolean { + const autoClosingPair = characterPairSupport.getAutoClosingPairs()[0]; + return autoClosingPair.shouldAutoClose(createFakeScopedLineTokens(line), column); } test('shouldAutoClosePair in empty line', () => { - let sup = new CharacterPairSupport({ autoClosingPairs: [{ open: '{', close: '}', notIn: ['string', 'comment'] }] }); - assert.strictEqual(testShouldAutoClose(sup, [], 'a', 1), false); - assert.strictEqual(testShouldAutoClose(sup, [], '{', 1), true); + const sup = new CharacterPairSupport({ autoClosingPairs: [{ open: '{', close: '}', notIn: ['string', 'comment'] }] }); + const tokenText: TokenText[] = []; + assert.strictEqual(testShouldAutoClose(sup, tokenText, 1), true); }); test('shouldAutoClosePair in not interesting line 1', () => { - let sup = new CharacterPairSupport({ autoClosingPairs: [{ open: '{', close: '}', notIn: ['string', 'comment'] }] }); - assert.strictEqual(testShouldAutoClose(sup, [{ text: 'do', type: StandardTokenType.Other }], '{', 3), true); - assert.strictEqual(testShouldAutoClose(sup, [{ text: 'do', type: StandardTokenType.Other }], 'a', 3), false); + const sup = new CharacterPairSupport({ autoClosingPairs: [{ open: '{', close: '}', notIn: ['string', 'comment'] }] }); + const tokenText: TokenText[] = [ + { text: 'do', type: StandardTokenType.Other } + ]; + assert.strictEqual(testShouldAutoClose(sup, tokenText, 3), true); }); test('shouldAutoClosePair in not interesting line 2', () => { - let sup = new CharacterPairSupport({ autoClosingPairs: [{ open: '{', close: '}' }] }); - assert.strictEqual(testShouldAutoClose(sup, [{ text: 'do', type: StandardTokenType.String }], '{', 3), true); - assert.strictEqual(testShouldAutoClose(sup, [{ text: 'do', type: StandardTokenType.String }], 'a', 3), false); + const sup = new CharacterPairSupport({ autoClosingPairs: [{ open: '{', close: '}' }] }); + const tokenText: TokenText[] = [ + { text: 'do', type: StandardTokenType.String } + ]; + assert.strictEqual(testShouldAutoClose(sup, tokenText, 3), true); }); test('shouldAutoClosePair in interesting line 1', () => { - let sup = new CharacterPairSupport({ autoClosingPairs: [{ open: '{', close: '}', notIn: ['string', 'comment'] }] }); - assert.strictEqual(testShouldAutoClose(sup, [{ text: '"a"', type: StandardTokenType.String }], '{', 1), false); - assert.strictEqual(testShouldAutoClose(sup, [{ text: '"a"', type: StandardTokenType.String }], 'a', 1), false); - assert.strictEqual(testShouldAutoClose(sup, [{ text: '"a"', type: StandardTokenType.String }], '{', 2), false); - assert.strictEqual(testShouldAutoClose(sup, [{ text: '"a"', type: StandardTokenType.String }], 'a', 2), false); - assert.strictEqual(testShouldAutoClose(sup, [{ text: '"a"', type: StandardTokenType.String }], '{', 3), false); - assert.strictEqual(testShouldAutoClose(sup, [{ text: '"a"', type: StandardTokenType.String }], 'a', 3), false); - assert.strictEqual(testShouldAutoClose(sup, [{ text: '"a"', type: StandardTokenType.String }], '{', 4), false); - assert.strictEqual(testShouldAutoClose(sup, [{ text: '"a"', type: StandardTokenType.String }], 'a', 4), false); + const sup = new CharacterPairSupport({ autoClosingPairs: [{ open: '{', close: '}', notIn: ['string', 'comment'] }] }); + const tokenText: TokenText[] = [ + { text: '"a"', type: StandardTokenType.String } + ]; + assert.strictEqual(testShouldAutoClose(sup, tokenText, 1), false); + assert.strictEqual(testShouldAutoClose(sup, tokenText, 2), false); + assert.strictEqual(testShouldAutoClose(sup, tokenText, 3), false); + assert.strictEqual(testShouldAutoClose(sup, tokenText, 4), false); }); test('shouldAutoClosePair in interesting line 2', () => { - let sup = new CharacterPairSupport({ autoClosingPairs: [{ open: '{', close: '}', notIn: ['string', 'comment'] }] }); - assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], '{', 1), true); - assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], 'a', 1), false); - assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], '{', 2), true); - assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], 'a', 2), false); - assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], '{', 3), true); - assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], 'a', 3), false); - assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], '{', 4), false); - assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], 'a', 4), false); - assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], '{', 5), false); - assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], 'a', 5), false); - assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], '{', 6), false); - assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], 'a', 6), false); - assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], '{', 7), true); - assert.strictEqual(testShouldAutoClose(sup, [{ text: 'x=', type: StandardTokenType.Other }, { text: '"a"', type: StandardTokenType.String }, { text: ';', type: StandardTokenType.Other }], 'a', 7), false); + const sup = new CharacterPairSupport({ autoClosingPairs: [{ open: '{', close: '}', notIn: ['string', 'comment'] }] }); + const tokenText: TokenText[] = [ + { text: 'x=', type: StandardTokenType.Other }, + { text: '"a"', type: StandardTokenType.String }, + { text: ';', type: StandardTokenType.Other } + ]; + assert.strictEqual(testShouldAutoClose(sup, tokenText, 1), true); + assert.strictEqual(testShouldAutoClose(sup, tokenText, 2), true); + assert.strictEqual(testShouldAutoClose(sup, tokenText, 3), true); + assert.strictEqual(testShouldAutoClose(sup, tokenText, 4), false); + assert.strictEqual(testShouldAutoClose(sup, tokenText, 5), false); + assert.strictEqual(testShouldAutoClose(sup, tokenText, 6), false); + assert.strictEqual(testShouldAutoClose(sup, tokenText, 7), true); }); test('shouldAutoClosePair in interesting line 3', () => { - let sup = new CharacterPairSupport({ autoClosingPairs: [{ open: '{', close: '}', notIn: ['string', 'comment'] }] }); - assert.strictEqual(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], '{', 1), true); - assert.strictEqual(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], 'a', 1), false); - assert.strictEqual(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], '{', 2), true); - assert.strictEqual(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], 'a', 2), false); - assert.strictEqual(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], '{', 3), false); - assert.strictEqual(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], 'a', 3), false); - assert.strictEqual(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], '{', 4), false); - assert.strictEqual(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], 'a', 4), false); - assert.strictEqual(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], '{', 5), false); - assert.strictEqual(testShouldAutoClose(sup, [{ text: ' ', type: StandardTokenType.Other }, { text: '//a', type: StandardTokenType.Comment }], 'a', 5), false); + const sup = new CharacterPairSupport({ autoClosingPairs: [{ open: '{', close: '}', notIn: ['string', 'comment'] }] }); + const tokenText: TokenText[] = [ + { text: ' ', type: StandardTokenType.Other }, + { text: '//a', type: StandardTokenType.Comment } + ]; + assert.strictEqual(testShouldAutoClose(sup, tokenText, 1), true); + assert.strictEqual(testShouldAutoClose(sup, tokenText, 2), true); + assert.strictEqual(testShouldAutoClose(sup, tokenText, 3), false); + assert.strictEqual(testShouldAutoClose(sup, tokenText, 4), false); + assert.strictEqual(testShouldAutoClose(sup, tokenText, 5), false); }); }); diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 2711e370a19..7716e2b73dd 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -3,12 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IStringDictionary } from 'vs/base/common/collections'; import { Event } from 'vs/base/common/event'; -import * as objects from 'vs/base/common/objects'; import * as types from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { Extensions, IConfigurationRegistry, overrideIdentifierFromKey, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; +import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -27,6 +25,16 @@ export interface IConfigurationOverrides { resource?: URI | null; } +export function isConfigurationUpdateOverrides(thing: any): thing is IConfigurationUpdateOverrides { + return thing + && typeof thing === 'object' + && (!thing.overrideIdentifiers || types.isArray(thing.overrideIdentifiers)) + && !thing.overrideIdentifier + && (!thing.resource || thing.resource instanceof URI); +} + +export type IConfigurationUpdateOverrides = Omit & { overrideIdentifiers?: string[] | null; }; + export const enum ConfigurationTarget { USER = 1, USER_LOCAL, @@ -108,9 +116,9 @@ export interface IConfigurationService { getValue(section: string, overrides: IConfigurationOverrides): T; updateValue(key: string, value: any): Promise; - updateValue(key: string, value: any, overrides: IConfigurationOverrides): Promise; + updateValue(key: string, value: any, overrides: IConfigurationOverrides | IConfigurationUpdateOverrides): Promise; updateValue(key: string, value: any, target: ConfigurationTarget): Promise; - updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget, donotNotifyError?: boolean): Promise; + updateValue(key: string, value: any, overrides: IConfigurationOverrides | IConfigurationUpdateOverrides, target: ConfigurationTarget, donotNotifyError?: boolean): Promise; inspect(key: string, overrides?: IConfigurationOverrides): IConfigurationValue>; @@ -151,89 +159,6 @@ export interface IConfigurationCompareResult { overrides: [string, string[]][]; } -export function compare(from: IConfigurationModel | undefined, to: IConfigurationModel | undefined): IConfigurationCompareResult { - const added = to - ? from ? to.keys.filter(key => from.keys.indexOf(key) === -1) : [...to.keys] - : []; - const removed = from - ? to ? from.keys.filter(key => to.keys.indexOf(key) === -1) : [...from.keys] - : []; - const updated: string[] = []; - - if (to && from) { - for (const key of from.keys) { - if (to.keys.indexOf(key) !== -1) { - const value1 = getConfigurationValue(from.contents, key); - const value2 = getConfigurationValue(to.contents, key); - if (!objects.equals(value1, value2)) { - updated.push(key); - } - } - } - } - - const overrides: [string, string[]][] = []; - const byOverrideIdentifier = (overrides: IOverrides[]): IStringDictionary => { - const result: IStringDictionary = {}; - for (const override of overrides) { - for (const identifier of override.identifiers) { - result[keyFromOverrideIdentifier(identifier)] = override; - } - } - return result; - }; - const toOverridesByIdentifier: IStringDictionary = to ? byOverrideIdentifier(to.overrides) : {}; - const fromOverridesByIdentifier: IStringDictionary = from ? byOverrideIdentifier(from.overrides) : {}; - - if (Object.keys(toOverridesByIdentifier).length) { - for (const key of added) { - const override = toOverridesByIdentifier[key]; - if (override) { - overrides.push([overrideIdentifierFromKey(key), override.keys]); - } - } - } - if (Object.keys(fromOverridesByIdentifier).length) { - for (const key of removed) { - const override = fromOverridesByIdentifier[key]; - if (override) { - overrides.push([overrideIdentifierFromKey(key), override.keys]); - } - } - } - - if (Object.keys(toOverridesByIdentifier).length && Object.keys(fromOverridesByIdentifier).length) { - for (const key of updated) { - const fromOverride = fromOverridesByIdentifier[key]; - const toOverride = toOverridesByIdentifier[key]; - if (fromOverride && toOverride) { - const result = compare({ contents: fromOverride.contents, keys: fromOverride.keys, overrides: [] }, { contents: toOverride.contents, keys: toOverride.keys, overrides: [] }); - overrides.push([overrideIdentifierFromKey(key), [...result.added, ...result.removed, ...result.updated]]); - } - } - } - - return { added, removed, updated, overrides }; -} - -export function toOverrides(raw: any, conflictReporter: (message: string) => void): IOverrides[] { - const overrides: IOverrides[] = []; - for (const key of Object.keys(raw)) { - if (OVERRIDE_PROPERTY_PATTERN.test(key)) { - const overrideRaw: any = {}; - for (const keyInOverrideRaw in raw[key]) { - overrideRaw[keyInOverrideRaw] = raw[key][keyInOverrideRaw]; - } - overrides.push({ - identifiers: [overrideIdentifierFromKey(key).trim()], - keys: Object.keys(overrideRaw), - contents: toValuesTree(overrideRaw, conflictReporter) - }); - } - } - return overrides; -} - export function toValuesTree(properties: { [qualifiedKey: string]: any }, conflictReporter: (message: string) => void): any { const root = Object.create(null); @@ -354,10 +279,6 @@ export function getDefaultValues(): any { return valueTreeRoot; } -export function keyFromOverrideIdentifier(overrideIdentifier: string): string { - return `[${overrideIdentifier}]`; -} - export function getMigratedSettingValue(configurationService: IConfigurationService, currentSettingName: string, legacySettingName: string): T { const setting = configurationService.inspect(currentSettingName); const legacySetting = configurationService.inspect(legacySettingName); diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index ae155ae126f..f41d440c4f9 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as arrays from 'vs/base/common/arrays'; +import { IStringDictionary } from 'vs/base/common/collections'; import { Emitter, Event } from 'vs/base/common/event'; import * as json from 'vs/base/common/json'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -12,8 +13,8 @@ import * as objects from 'vs/base/common/objects'; import { IExtUri } from 'vs/base/common/resources'; import * as types from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { addToValueTree, compare, ConfigurationTarget, getConfigurationKeys, getConfigurationValue, getDefaultValues, IConfigurationChange, IConfigurationChangeEvent, IConfigurationData, IConfigurationModel, IConfigurationOverrides, IConfigurationValue, IOverrides, removeFromValueTree, toOverrides, toValuesTree } from 'vs/platform/configuration/common/configuration'; -import { ConfigurationScope, Extensions, IConfigurationPropertySchema, IConfigurationRegistry, overrideIdentifierFromKey, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; +import { addToValueTree, ConfigurationTarget, getConfigurationKeys, getConfigurationValue, getDefaultValues, IConfigurationChange, IConfigurationChangeEvent, IConfigurationCompareResult, IConfigurationData, IConfigurationModel, IConfigurationOverrides, IConfigurationUpdateOverrides, IConfigurationValue, IOverrides, removeFromValueTree, toValuesTree } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationScope, Extensions, IConfigurationPropertySchema, IConfigurationRegistry, overrideIdentifiersFromKey, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; import { IFileService } from 'vs/platform/files/common/files'; import { Registry } from 'vs/platform/registry/common/platform'; import { Workspace } from 'vs/platform/workspace/common/workspace'; @@ -58,12 +59,21 @@ export class ConfigurationModel implements IConfigurationModel { } getKeysForOverrideIdentifier(identifier: string): string[] { + const keys: string[] = []; for (const override of this.overrides) { - if (override.identifiers.indexOf(identifier) !== -1) { - return override.keys; + if (override.identifiers.includes(identifier)) { + keys.push(...override.keys); } } - return []; + return arrays.distinct(keys); + } + + getAllOverrideIdentifiers(): string[] { + const result: string[] = []; + for (const override of this.overrides) { + result.push(...override.identifiers); + } + return arrays.distinct(result); } override(identifier: string): ConfigurationModel { @@ -87,6 +97,8 @@ export class ConfigurationModel implements IConfigurationModel { const [override] = overrides.filter(o => arrays.equals(o.identifiers, otherOverride.identifiers)); if (override) { this.mergeContents(override.contents, otherOverride.contents); + override.keys.push(...otherOverride.keys); + override.keys = arrays.distinct(override.keys); } else { overrides.push(objects.deepClone(otherOverride)); } @@ -156,12 +168,27 @@ export class ConfigurationModel implements IConfigurationModel { } private getContentsForOverrideIdentifer(identifier: string): any { + let contentsForIdentifierOnly: IStringDictionary | null = null; + let contents: IStringDictionary | null = null; + const mergeContents = (contentsToMerge: any) => { + if (contentsToMerge) { + if (contents) { + this.mergeContents(contents, contentsToMerge); + } else { + contents = objects.deepClone(contentsToMerge); + } + } + }; for (const override of this.overrides) { - if (override.identifiers.indexOf(identifier) !== -1) { - return override.contents; + if (arrays.equals(override.identifiers, [identifier])) { + contentsForIdentifierOnly = override.contents; + } else if (override.identifiers.includes(identifier)) { + mergeContents(override.contents); } } - return null; + // Merge contents of the identifier only at the end to take precedence. + mergeContents(contentsForIdentifierOnly); + return contents; } toJSON(): IConfigurationModel { @@ -212,9 +239,9 @@ export class DefaultConfigurationModel extends ConfigurationModel { const keys = getConfigurationKeys(); const overrides: IOverrides[] = []; for (const key of Object.keys(contents)) { - if (OVERRIDE_PROPERTY_PATTERN.test(key)) { + if (OVERRIDE_PROPERTY_REGEX.test(key)) { overrides.push({ - identifiers: [overrideIdentifierFromKey(key).trim()], + identifiers: overrideIdentifiersFromKey(key), keys: Object.keys(contents[key]), contents: toValuesTree(contents[key], message => console.error(`Conflict in default settings file: ${message}`)), }); @@ -333,7 +360,7 @@ export class ConfigurationModelParser { raw = filtered.raw; const contents = toValuesTree(raw, message => console.error(`Conflict in settings file ${this._name}: ${message}`)); const keys = Object.keys(raw); - const overrides: IOverrides[] = toOverrides(raw, message => console.error(`Conflict in settings file ${this._name}: ${message}`)); + const overrides = this.toOverrides(raw, message => console.error(`Conflict in settings file ${this._name}: ${message}`)); return { contents, keys, overrides, restricted: filtered.restricted }; } @@ -344,7 +371,7 @@ export class ConfigurationModelParser { const raw: any = {}; const restricted: string[] = []; for (let key in properties) { - if (OVERRIDE_PROPERTY_PATTERN.test(key) && filterOverriddenProperties) { + if (OVERRIDE_PROPERTY_REGEX.test(key) && filterOverriddenProperties) { const result = this.filter(properties[key], configurationProperties, false, options); raw[key] = result.raw; restricted.push(...result.restricted); @@ -365,6 +392,24 @@ export class ConfigurationModelParser { return { raw, restricted }; } + private toOverrides(raw: any, conflictReporter: (message: string) => void): IOverrides[] { + const overrides: IOverrides[] = []; + for (const key of Object.keys(raw)) { + if (OVERRIDE_PROPERTY_REGEX.test(key)) { + const overrideRaw: any = {}; + for (const keyInOverrideRaw in raw[key]) { + overrideRaw[keyInOverrideRaw] = raw[key][keyInOverrideRaw]; + } + overrides.push({ + identifiers: overrideIdentifiersFromKey(key), + keys: Object.keys(overrideRaw), + contents: toValuesTree(overrideRaw, conflictReporter) + }); + } + } + return overrides; + } + } export class UserSettings extends Disposable { @@ -431,7 +476,7 @@ export class Configuration { return consolidateConfigurationModel.getValue(section); } - updateValue(key: string, value: any, overrides: IConfigurationOverrides = {}): void { + updateValue(key: string, value: any, overrides: IConfigurationUpdateOverrides = {}): void { let memoryConfiguration: ConfigurationModel | undefined; if (overrides.resource) { memoryConfiguration = this._memoryConfigurationByResource.get(overrides.resource); @@ -543,10 +588,9 @@ export class Configuration { } compareAndUpdateDefaultConfiguration(defaults: ConfigurationModel, keys: string[]): IConfigurationChange { - const overrides: [string, string[]][] = keys - .filter(key => OVERRIDE_PROPERTY_PATTERN.test(key)) - .map(key => { - const overrideIdentifier = overrideIdentifierFromKey(key); + const overrides: [string, string[]][] = []; + for (const key of keys) { + for (const overrideIdentifier of overrideIdentifiersFromKey(key)) { const fromKeys = this._defaultConfiguration.getKeysForOverrideIdentifier(overrideIdentifier); const toKeys = defaults.getKeysForOverrideIdentifier(overrideIdentifier); const keys = [ @@ -554,8 +598,9 @@ export class Configuration { ...fromKeys.filter(key => toKeys.indexOf(key) === -1), ...fromKeys.filter(key => !objects.equals(this._defaultConfiguration.override(overrideIdentifier).getValue(key), defaults.override(overrideIdentifier).getValue(key))) ]; - return [overrideIdentifier, keys]; - }); + overrides.push([overrideIdentifier, keys]); + } + } this.updateDefaultConfiguration(defaults); return { keys, overrides }; } @@ -732,6 +777,15 @@ export class Configuration { return [...keys.values()]; } + protected allOverrideIdentifiers(): string[] { + const keys: Set = new Set(); + this._defaultConfiguration.freeze().getAllOverrideIdentifiers().forEach(key => keys.add(key)); + this.userConfiguration.freeze().getAllOverrideIdentifiers().forEach(key => keys.add(key)); + this._workspaceConfiguration.freeze().getAllOverrideIdentifiers().forEach(key => keys.add(key)); + this._folderConfigurations.forEach(folderConfiguraiton => folderConfiguraiton.freeze().getAllOverrideIdentifiers().forEach(key => keys.add(key))); + return [...keys.values()]; + } + protected getAllKeysForOverrideIdentifier(overrideIdentifier: string): string[] { const keys: Set = new Set(); this._defaultConfiguration.getKeysForOverrideIdentifier(overrideIdentifier).forEach(key => keys.add(key)); @@ -839,3 +893,59 @@ export class AllKeysConfigurationChangeEvent extends ConfigurationChangeEvent { this.sourceConfig = sourceConfig; } } + +function compare(from: ConfigurationModel | undefined, to: ConfigurationModel | undefined): IConfigurationCompareResult { + const { added, removed, updated } = compareConfigurationContents(to, from); + const overrides: [string, string[]][] = []; + + const fromOverrideIdentifiers = from?.getAllOverrideIdentifiers() || []; + const toOverrideIdentifiers = to?.getAllOverrideIdentifiers() || []; + + if (to) { + const addedOverrideIdentifiers = toOverrideIdentifiers.filter(key => !fromOverrideIdentifiers.includes(key)); + for (const identifier of addedOverrideIdentifiers) { + overrides.push([identifier, to.getKeysForOverrideIdentifier(identifier)]); + } + } + + if (from) { + const removedOverrideIdentifiers = fromOverrideIdentifiers.filter(key => !toOverrideIdentifiers.includes(key)); + for (const identifier of removedOverrideIdentifiers) { + overrides.push([identifier, from.getKeysForOverrideIdentifier(identifier)]); + } + } + + if (to && from) { + for (const identifier of fromOverrideIdentifiers) { + if (toOverrideIdentifiers.includes(identifier)) { + const result = compareConfigurationContents({ contents: from.getOverrideValue(undefined, identifier) || {}, keys: from.getKeysForOverrideIdentifier(identifier) }, { contents: to.getOverrideValue(undefined, identifier) || {}, keys: to.getKeysForOverrideIdentifier(identifier) }); + overrides.push([identifier, [...result.added, ...result.removed, ...result.updated]]); + } + } + } + + return { added, removed, updated, overrides }; +} + +function compareConfigurationContents(to: { keys: string[], contents: any } | undefined, from: { keys: string[], contents: any } | undefined) { + const added = to + ? from ? to.keys.filter(key => from.keys.indexOf(key) === -1) : [...to.keys] + : []; + const removed = from + ? to ? from.keys.filter(key => to.keys.indexOf(key) === -1) : [...from.keys] + : []; + const updated: string[] = []; + + if (to && from) { + for (const key of from.keys) { + if (to.keys.indexOf(key) !== -1) { + const value1 = getConfigurationValue(from.contents, key); + const value2 = getConfigurationValue(to.contents, key); + if (!objects.equals(value1, value2)) { + updated.push(key); + } + } + } + } + return { added, removed, updated }; +} diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 16ae308adf1..6dc55505e20 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -217,6 +217,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { this.excludedConfigurationProperties = {}; contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema); + this.registerOverridePropertyPatternKey(); } public registerConfiguration(configuration: IConfigurationNode, validate: boolean = true): void { @@ -257,7 +258,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { for (const key in defaultConfiguration) { properties.push(key); - if (OVERRIDE_PROPERTY_PATTERN.test(key)) { + if (OVERRIDE_PROPERTY_REGEX.test(key)) { this.defaultValues[key] = { ...(this.defaultValues[key] || {}), ...defaultConfiguration[key] }; const property: IConfigurationPropertySchema = { type: 'object', @@ -265,7 +266,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { description: nls.localize('defaultLanguageConfiguration.description', "Configure settings to be overridden for {0} language.", key), $ref: resourceLanguageSettingsSchemaId }; - overrideIdentifiers.push(overrideIdentifierFromKey(key)); + overrideIdentifiers.push(...overrideIdentifiersFromKey(key)); this.configurationProperties[key] = property; this.defaultLanguageConfigurationOverridesNode.properties![key] = property; } else { @@ -290,7 +291,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { for (const key in defaultConfiguration) { properties.push(key); delete this.defaultValues[key]; - if (OVERRIDE_PROPERTY_PATTERN.test(key)) { + if (OVERRIDE_PROPERTY_REGEX.test(key)) { delete this.configurationProperties[key]; delete this.defaultLanguageConfigurationOverridesNode.properties![key]; } else { @@ -370,7 +371,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { this.updatePropertyDefaultValue(key, property); // update scope - if (OVERRIDE_PROPERTY_PATTERN.test(key)) { + if (OVERRIDE_PROPERTY_REGEX.test(key)) { property.scope = undefined; // No scope for overridable properties `[${identifier}]` } else { property.scope = types.isUndefinedOrNull(property.scope) ? scope : property.scope; @@ -499,6 +500,22 @@ class ConfigurationRegistry implements IConfigurationRegistry { this._onDidSchemaChange.fire(); } + private registerOverridePropertyPatternKey(): void { + const resourceLanguagePropertiesSchema: IJSONSchema = { + type: 'object', + description: nls.localize('overrideSettings.defaultDescription', "Configure editor settings to be overridden for a language."), + errorMessage: nls.localize('overrideSettings.errorMessage', "This setting does not support per-language configuration."), + $ref: resourceLanguageSettingsSchemaId, + }; + allSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema; + applicationSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema; + machineSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema; + machineOverridableSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema; + windowSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema; + resourceSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema; + this._onDidSchemaChange.fire(); + } + private updatePropertyDefaultValue(key: string, property: IConfigurationPropertySchema): void { let defaultValue = this.defaultValues[key]; if (types.isUndefined(defaultValue)) { @@ -511,11 +528,28 @@ class ConfigurationRegistry implements IConfigurationRegistry { } } -const OVERRIDE_PROPERTY = '\\[.*\\]$'; -export const OVERRIDE_PROPERTY_PATTERN = new RegExp(OVERRIDE_PROPERTY); +const OVERRIDE_IDENTIFIER_PATTERN = `\\[([^\\]]+)\\]`; +const OVERRIDE_IDENTIFIER_REGEX = new RegExp(OVERRIDE_IDENTIFIER_PATTERN, 'g'); +export const OVERRIDE_PROPERTY_PATTERN = `^(${OVERRIDE_IDENTIFIER_PATTERN})+$`; +export const OVERRIDE_PROPERTY_REGEX = new RegExp(OVERRIDE_PROPERTY_PATTERN); -export function overrideIdentifierFromKey(key: string): string { - return key.substring(1, key.length - 1); +export function overrideIdentifiersFromKey(key: string): string[] { + const identifiers: string[] = []; + if (OVERRIDE_PROPERTY_REGEX.test(key)) { + let matches = OVERRIDE_IDENTIFIER_REGEX.exec(key); + while (matches?.length) { + const identifier = matches[1].trim(); + if (identifier) { + identifiers.push(identifier); + } + matches = OVERRIDE_IDENTIFIER_REGEX.exec(key); + } + } + return distinct(identifiers); +} + +export function keyFromOverrideIdentifiers(overrideIdentifiers: string[]): string { + return overrideIdentifiers.reduce((result, overrideIdentifier) => `${result}[${overrideIdentifier}]`, ''); } export function getDefaultValue(type: string | string[] | undefined): any { @@ -537,7 +571,6 @@ export function getDefaultValue(type: string | string[] | undefined): any { } } - const configurationRegistry = new ConfigurationRegistry(); Registry.add(Extensions.Configuration, configurationRegistry); @@ -545,7 +578,7 @@ export function validateProperty(property: string): string | null { if (!property.trim()) { return nls.localize('config.property.empty', "Cannot register an empty property"); } - if (OVERRIDE_PROPERTY_PATTERN.test(property)) { + if (OVERRIDE_PROPERTY_REGEX.test(property)) { return nls.localize('config.property.languageDefault', "Cannot register '{0}'. This matches property pattern '\\\\[.*\\\\]$' for describing language specific editor settings. Use 'configurationDefaults' contribution.", property); } if (configurationRegistry.getConfigurationProperties()[property] !== undefined) { diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index 0032a1e5b9f..406f48b8092 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -12,6 +12,35 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace'; +suite('ConfigurationModelParser', () => { + + test('parse configuration model with single override identifier', () => { + const testObject = new ConfigurationModelParser(''); + + testObject.parse(JSON.stringify({ '[x]': { 'a': 1 } })); + + assert.deepStrictEqual(JSON.stringify(testObject.configurationModel.overrides), JSON.stringify([{ identifiers: ['x'], keys: ['a'], contents: { 'a': 1 } }])); + }); + + test('parse configuration model with multiple override identifiers', () => { + const testObject = new ConfigurationModelParser(''); + + testObject.parse(JSON.stringify({ '[x][y]': { 'a': 1 } })); + + assert.deepStrictEqual(JSON.stringify(testObject.configurationModel.overrides), JSON.stringify([{ identifiers: ['x', 'y'], keys: ['a'], contents: { 'a': 1 } }])); + }); + + test('parse configuration model with multiple duplicate override identifiers', () => { + const testObject = new ConfigurationModelParser(''); + + testObject.parse(JSON.stringify({ '[x][y][x][z]': { 'a': 1 } })); + + assert.deepStrictEqual(JSON.stringify(testObject.configurationModel.overrides), JSON.stringify([{ identifiers: ['x', 'y', 'z'], keys: ['a'], contents: { 'a': 1 } }])); + }); + + +}); + suite('ConfigurationModel', () => { test('setValue for a key that has no sections and not defined', () => { @@ -190,7 +219,7 @@ suite('ConfigurationModel', () => { let result = base.merge(add); assert.deepStrictEqual(result.contents, { 'a': { 'b': 2 } }); - assert.deepStrictEqual(result.overrides, [{ identifiers: ['c'], contents: { 'a': 2, 'b': 2 }, keys: ['a'] }]); + assert.deepStrictEqual(result.overrides, [{ identifiers: ['c'], contents: { 'a': 2, 'b': 2 }, keys: ['a', 'b'] }]); assert.deepStrictEqual(result.override('c').contents, { 'a': 2, 'b': 2 }); assert.deepStrictEqual(result.keys, ['a.b']); }); @@ -236,6 +265,45 @@ suite('ConfigurationModel', () => { assert.deepStrictEqual(testObject.override('b').contents, { 'a': 2, 'c': 1 }); }); + + test('Test override when an override has multiple identifiers', () => { + const testObject = new ConfigurationModel({ 'a': 1, 'c': 1 }, ['a', 'c'], [{ identifiers: ['x', 'y'], contents: { 'a': 2 }, keys: ['a'] }]); + + let actual = testObject.override('x'); + assert.deepStrictEqual(actual.contents, { 'a': 2, 'c': 1 }); + assert.deepStrictEqual(actual.keys, ['a', 'c']); + assert.deepStrictEqual(testObject.getKeysForOverrideIdentifier('x'), ['a']); + + actual = testObject.override('y'); + assert.deepStrictEqual(actual.contents, { 'a': 2, 'c': 1 }); + assert.deepStrictEqual(actual.keys, ['a', 'c']); + assert.deepStrictEqual(testObject.getKeysForOverrideIdentifier('y'), ['a']); + }); + + test('Test override when an identifier is defined in multiple overrides', () => { + const testObject = new ConfigurationModel({ 'a': 1, 'c': 1 }, ['a', 'c'], [{ identifiers: ['x'], contents: { 'a': 3, 'b': 1 }, keys: ['a', 'b'] }, { identifiers: ['x', 'y'], contents: { 'a': 2 }, keys: ['a'] }]); + + const actual = testObject.override('x'); + assert.deepStrictEqual(actual.contents, { 'a': 3, 'c': 1, 'b': 1 }); + assert.deepStrictEqual(actual.keys, ['a', 'c']); + + assert.deepStrictEqual(testObject.getKeysForOverrideIdentifier('x'), ['a', 'b']); + }); + + test('Test merge when configuration models have multiple identifiers', () => { + const testObject = new ConfigurationModel({ 'a': 1, 'c': 1 }, ['a', 'c'], [{ identifiers: ['y'], contents: { 'c': 1 }, keys: ['c'] }, { identifiers: ['x', 'y'], contents: { 'a': 2 }, keys: ['a'] }]); + const target = new ConfigurationModel({ 'a': 2, 'b': 1 }, ['a', 'b'], [{ identifiers: ['x'], contents: { 'a': 3, 'b': 2 }, keys: ['a', 'b'] }, { identifiers: ['x', 'y'], contents: { 'b': 3 }, keys: ['b'] }]); + + const actual = testObject.merge(target); + + assert.deepStrictEqual(actual.contents, { 'a': 2, 'c': 1, 'b': 1 }); + assert.deepStrictEqual(actual.keys, ['a', 'c', 'b']); + assert.deepStrictEqual(actual.overrides, [ + { identifiers: ['y'], contents: { 'c': 1 }, keys: ['c'] }, + { identifiers: ['x', 'y'], contents: { 'a': 2, 'b': 3 }, keys: ['a', 'b'] }, + { identifiers: ['x'], contents: { 'a': 3, 'b': 2 }, keys: ['a', 'b'] }, + ]); + }); }); suite('CustomConfigurationModel', () => { @@ -582,11 +650,14 @@ suite('ConfigurationChangeEvent', () => { 'files.autoSave': 'off', '[markdown]': { 'editor.wordWrap': 'off' + }, + '[typescript][jsonc]': { + 'editor.lineNumbers': 'off' } })); let testObject = new ConfigurationChangeEvent(change, undefined, configuration); - assert.deepStrictEqual(testObject.affectedKeys, ['files.autoSave', '[markdown]', 'editor.wordWrap']); + assert.deepStrictEqual(testObject.affectedKeys, ['files.autoSave', '[markdown]', '[typescript][jsonc]', 'editor.wordWrap', 'editor.lineNumbers']); assert.ok(testObject.affectsConfiguration('files')); assert.ok(testObject.affectsConfiguration('files.autoSave')); @@ -598,8 +669,16 @@ suite('ConfigurationChangeEvent', () => { assert.ok(testObject.affectsConfiguration('editor')); assert.ok(testObject.affectsConfiguration('editor.wordWrap')); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers')); assert.ok(testObject.affectsConfiguration('editor', { overrideIdentifier: 'markdown' })); + assert.ok(testObject.affectsConfiguration('editor', { overrideIdentifier: 'jsonc' })); + assert.ok(testObject.affectsConfiguration('editor', { overrideIdentifier: 'typescript' })); assert.ok(testObject.affectsConfiguration('editor.wordWrap', { overrideIdentifier: 'markdown' })); + assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { overrideIdentifier: 'jsonc' })); + assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { overrideIdentifier: 'typescript' })); + assert.ok(!testObject.affectsConfiguration('editor.lineNumbers', { overrideIdentifier: 'markdown' })); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { overrideIdentifier: 'typescript' })); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { overrideIdentifier: 'jsonc' })); assert.ok(!testObject.affectsConfiguration('editor', { overrideIdentifier: 'json' })); assert.ok(!testObject.affectsConfiguration('editor.fontSize', { overrideIdentifier: 'markdown' })); @@ -615,6 +694,10 @@ suite('ConfigurationChangeEvent', () => { 'editor.fontSize': 12, 'editor.wordWrap': 'off' }, + '[css][scss]': { + 'editor.lineNumbers': 'off', + 'css.lint.emptyRules': 'error' + }, 'files.autoSave': 'off', })); const data = configuration.toData(); @@ -624,11 +707,15 @@ suite('ConfigurationChangeEvent', () => { 'editor.fontSize': 13, 'editor.wordWrap': 'off' }, + '[css][scss]': { + 'editor.lineNumbers': 'relative', + 'css.lint.emptyRules': 'error' + }, 'window.zoomLevel': 1, })); let testObject = new ConfigurationChangeEvent(change, { data }, configuration); - assert.deepStrictEqual(testObject.affectedKeys, ['window.zoomLevel', '[markdown]', 'workbench.editor.enablePreview', 'editor.fontSize']); + assert.deepStrictEqual(testObject.affectedKeys, ['window.zoomLevel', '[markdown]', '[css][scss]', 'workbench.editor.enablePreview', 'editor.fontSize', 'editor.lineNumbers']); assert.ok(!testObject.affectsConfiguration('files')); @@ -637,10 +724,18 @@ suite('ConfigurationChangeEvent', () => { assert.ok(!testObject.affectsConfiguration('[markdown].editor.fontSize')); assert.ok(!testObject.affectsConfiguration('[markdown].editor.wordWrap')); assert.ok(!testObject.affectsConfiguration('[markdown].workbench')); + assert.ok(testObject.affectsConfiguration('[css][scss]')); assert.ok(testObject.affectsConfiguration('editor')); assert.ok(testObject.affectsConfiguration('editor', { overrideIdentifier: 'markdown' })); + assert.ok(testObject.affectsConfiguration('editor', { overrideIdentifier: 'css' })); + assert.ok(testObject.affectsConfiguration('editor', { overrideIdentifier: 'scss' })); assert.ok(testObject.affectsConfiguration('editor.fontSize', { overrideIdentifier: 'markdown' })); + assert.ok(!testObject.affectsConfiguration('editor.fontSize', { overrideIdentifier: 'css' })); + assert.ok(!testObject.affectsConfiguration('editor.fontSize', { overrideIdentifier: 'scss' })); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { overrideIdentifier: 'scss' })); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { overrideIdentifier: 'css' })); + assert.ok(!testObject.affectsConfiguration('editor.lineNumbers', { overrideIdentifier: 'markdown' })); assert.ok(!testObject.affectsConfiguration('editor.wordWrap')); assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { overrideIdentifier: 'markdown' })); assert.ok(!testObject.affectsConfiguration('editor', { overrideIdentifier: 'json' })); diff --git a/src/vs/platform/driver/browser/baseDriver.ts b/src/vs/platform/driver/browser/baseDriver.ts index 90ef43a186a..40e5e2227ab 100644 --- a/src/vs/platform/driver/browser/baseDriver.ts +++ b/src/vs/platform/driver/browser/baseDriver.ts @@ -57,7 +57,6 @@ export abstract class BaseWindowDriver implements IWindowDriver { async getElements(selector: string, recursive: boolean): Promise { const query = document.querySelectorAll(selector); const result: IElement[] = []; - for (let i = 0; i < query.length; i++) { const element = query.item(i); result.push(this.serializeElement(element, recursive)); @@ -139,9 +138,8 @@ export abstract class BaseWindowDriver implements IWindowDriver { } const lines: string[] = []; - - for (let i = 0; i < xterm.buffer.length; i++) { - lines.push(xterm.buffer.getLine(i)!.translateToString(true)); + for (let i = 0; i < xterm.buffer.active.length; i++) { + lines.push(xterm.buffer.active.getLine(i)!.translateToString(true)); } return lines; diff --git a/src/vs/platform/driver/common/driver.ts b/src/vs/platform/driver/common/driver.ts index e447cee2532..41b7809f448 100644 --- a/src/vs/platform/driver/common/driver.ts +++ b/src/vs/platform/driver/common/driver.ts @@ -57,10 +57,6 @@ export interface IDriver { getLocaleInfo(windowId: number): Promise; getLocalizedStrings(windowId: number): Promise; } -//*END - -export const ID = 'driverService'; -export const IDriver = createDecorator(ID); export interface IWindowDriver { click(selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise; @@ -76,6 +72,10 @@ export interface IWindowDriver { getLocaleInfo(): Promise; getLocalizedStrings(): Promise } +//*END + +export const ID = 'driverService'; +export const IDriver = createDecorator(ID); export interface IDriverOptions { verbose: boolean; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 821cbf257f6..763442884a9 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -290,14 +290,16 @@ function wrapText(text: string, columns: number): string[] { return lines; } -export function buildHelpMessage(productName: string, executableName: string, version: string, options: OptionDescriptions, isPipeSupported = true): string { +export function buildHelpMessage(productName: string, executableName: string, version: string, options: OptionDescriptions, capabilities?: { noPipe?: boolean, noInputFiles: boolean }): string { const columns = (process.stdout).isTTY && (process.stdout).columns || 80; - let help = [`${productName} ${version}`]; + const inputFiles = capabilities?.noInputFiles !== true ? `[${localize('paths', 'paths')}...]` : ''; + + const help = [`${productName} ${version}`]; help.push(''); - help.push(`${localize('usage', "Usage")}: ${executableName} [${localize('options', "options")}][${localize('paths', 'paths')}...]`); + help.push(`${localize('usage', "Usage")}: ${executableName} [${localize('options', "options")}]${inputFiles}`); help.push(''); - if (isPipeSupported) { + if (capabilities?.noPipe !== true) { if (isWindows) { help.push(localize('stdinWindows', "To read output from another program, append '-' (e.g. 'echo Hello World | {0} -')", executableName)); } else { diff --git a/src/vs/platform/environment/test/node/nativeModules.test.ts b/src/vs/platform/environment/test/node/nativeModules.test.ts index 130d0561679..f1379a6ff6f 100644 --- a/src/vs/platform/environment/test/node/nativeModules.test.ts +++ b/src/vs/platform/environment/test/node/nativeModules.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { isWindows } from 'vs/base/common/platform'; +import { isLinux, isWindows } from 'vs/base/common/platform'; function testErrorMessage(module: string): string { return `Unable to load "${module}" dependency. It was probably not compiled for the right operating system architecture or had missing build tools.`; @@ -37,22 +37,39 @@ suite('Native Modules (all platforms)', () => { assert.ok(typeof spdlog.createRotatingLogger === 'function', testErrorMessage('spdlog')); }); - test('nsfw', async () => { - const nsfWatcher = await import('vscode-nsfw'); - assert.ok(typeof nsfWatcher === 'function', testErrorMessage('nsfw')); - }); - - test('parcel', async () => { + test('@parcel/watcher', async () => { const parcelWatcher = await import('@parcel/watcher'); assert.ok(typeof parcelWatcher.subscribe === 'function', testErrorMessage('parcel')); }); - test('sqlite3', async () => { + test('@vscode/sqlite3', async () => { const sqlite3 = await import('@vscode/sqlite3'); assert.ok(typeof sqlite3.Database === 'function', testErrorMessage('@vscode/sqlite3')); }); }); +(isLinux ? suite.skip : suite)('Native Modules (Windows, macOS)', () => { + + test('keytar', async () => { + const keytar = await import('keytar'); + const name = `VSCode Test ${Math.floor(Math.random() * 1e9)}`; + try { + await keytar.setPassword(name, 'foo', 'bar'); + assert.strictEqual(await keytar.findPassword(name), 'bar'); + assert.strictEqual((await keytar.findCredentials(name)).length, 1); + assert.strictEqual(await keytar.getPassword(name, 'foo'), 'bar'); + await keytar.deletePassword(name, 'foo'); + assert.strictEqual(await keytar.getPassword(name, 'foo'), null); + } catch (err) { + try { + await keytar.deletePassword(name, 'foo'); // try to clean up + } catch { } + + throw err; + } + }); +}); + (!isWindows ? suite.skip : suite)('Native Modules (Windows)', () => { test('windows-mutex', async () => { @@ -77,7 +94,9 @@ suite('Native Modules (all platforms)', () => { }); test('vscode-windows-ca-certs', async () => { - // @ts-ignore Windows only + // @ts-ignore we do not directly depend on this module anymore + // but indirectly from our dependency to `vscode-proxy-agent` + // we still want to ensure this module can work properly. const windowsCerts = await import('vscode-windows-ca-certs'); const store = new windowsCerts.Crypt32(); assert.ok(windowsCerts, testErrorMessage('vscode-windows-ca-certs')); diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 60e7e067f08..c4496b5a9f0 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -584,12 +584,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi return isEngineValid(engine, this.productService.version, this.productService.date); } - query(token: CancellationToken): Promise>; - query(options: IQueryOptions, token: CancellationToken): Promise>; - async query(arg1: any, arg2?: any): Promise> { - const options: IQueryOptions = CancellationToken.isCancellationToken(arg1) ? {} : arg1; - const token: CancellationToken = CancellationToken.isCancellationToken(arg1) ? arg1 : arg2; - + async query(options: IQueryOptions, token: CancellationToken): Promise> { if (!this.isEnabled()) { throw new Error('No extension gallery service configured.'); } diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 35707069f50..ba9e7cfe606 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -323,7 +323,6 @@ export const IExtensionGalleryService = createDecorator>; query(options: IQueryOptions, token: CancellationToken): Promise>; getExtensions(identifiers: ReadonlyArray, token: CancellationToken): Promise; download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise; diff --git a/src/vs/platform/extensions/electron-main/directMainProcessExtensionHostStarter.ts b/src/vs/platform/extensions/electron-main/directMainProcessExtensionHostStarter.ts deleted file mode 100644 index a238332b86d..00000000000 --- a/src/vs/platform/extensions/electron-main/directMainProcessExtensionHostStarter.ts +++ /dev/null @@ -1,29 +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 { ExtensionHostStarter, IPartialLogService } from 'vs/platform/extensions/node/extensionHostStarter'; -import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; -import { ILogService } from 'vs/platform/log/common/log'; - -export class DirectMainProcessExtensionHostStarter extends ExtensionHostStarter { - - constructor( - @ILogService logService: IPartialLogService, - @ILifecycleMainService lifecycleMainService: ILifecycleMainService - ) { - super(logService); - - // Abnormal shutdown: terminate extension hosts asap - lifecycleMainService.onWillKill(() => { - this.killAllNow(); - }); - - // Normal shutdown: gracefully await extension host shutdowns - lifecycleMainService.onWillShutdown((e) => { - e.join(this.waitForAllExit(6000)); - }); - } - -} diff --git a/src/vs/platform/extensions/electron-main/workerMainProcessExtensionHostStarter.ts b/src/vs/platform/extensions/electron-main/workerMainProcessExtensionHostStarter.ts index 2abb2eb9dfd..dfc7164026f 100644 --- a/src/vs/platform/extensions/electron-main/workerMainProcessExtensionHostStarter.ts +++ b/src/vs/platform/extensions/electron-main/workerMainProcessExtensionHostStarter.ts @@ -11,9 +11,8 @@ import { FileAccess } from 'vs/base/common/network'; import { ILogService } from 'vs/platform/log/common/log'; import { Worker } from 'worker_threads'; import { IWorker, IWorkerCallback, IWorkerFactory, SimpleWorkerClient } from 'vs/base/common/worker/simpleWorker'; -import { IExtensionHostStarterWorkerHost } from 'vs/platform/extensions/node/extensionHostStarterWorker'; +import type { ExtensionHostStarter, IExtensionHostStarterWorkerHost } from 'vs/platform/extensions/node/extensionHostStarterWorker'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; -import { ExtensionHostStarter } from 'vs/platform/extensions/node/extensionHostStarter'; class NodeWorker implements IWorker { @@ -80,15 +79,7 @@ export class WorkerMainProcessExtensionHostStarter implements IDisposable, IExte ); this._initialize(); - // Abnormal shutdown: terminate extension hosts asap - lifecycleMainService.onWillKill(async () => { - this._shutdown = true; - if (this._proxy) { - this._proxy.killAllNow(); - } - }); - - // Normal shutdown: gracefully await extension host shutdowns + // On shutdown: gracefully await extension host shutdowns lifecycleMainService.onWillShutdown((e) => { this._shutdown = true; if (this._proxy) { diff --git a/src/vs/platform/extensions/node/extensionHostStarter.ts b/src/vs/platform/extensions/node/extensionHostStarter.ts deleted file mode 100644 index c71a39fb46d..00000000000 --- a/src/vs/platform/extensions/node/extensionHostStarter.ts +++ /dev/null @@ -1,237 +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 { SerializedError, transformErrorForSerialization } from 'vs/base/common/errors'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter'; -import { Emitter, Event } from 'vs/base/common/event'; -import { ChildProcess, fork } from 'child_process'; -import { FileAccess } from 'vs/base/common/network'; -import { StringDecoder } from 'string_decoder'; -import * as platform from 'vs/base/common/platform'; -import { ILogService } from 'vs/platform/log/common/log'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { mixin } from 'vs/base/common/objects'; -import { cwd } from 'vs/base/common/process'; -import { StopWatch } from 'vs/base/common/stopwatch'; -import { Promises, timeout } from 'vs/base/common/async'; - -export interface IPartialLogService { - readonly _serviceBrand: undefined; - info(message: string): void; -} - -class ExtensionHostProcess extends Disposable { - - readonly _onStdout = this._register(new Emitter()); - readonly onStdout = this._onStdout.event; - - readonly _onStderr = this._register(new Emitter()); - readonly onStderr = this._onStderr.event; - - readonly _onMessage = this._register(new Emitter()); - readonly onMessage = this._onMessage.event; - - readonly _onError = this._register(new Emitter<{ error: SerializedError; }>()); - readonly onError = this._onError.event; - - readonly _onExit = this._register(new Emitter<{ pid: number; code: number; signal: string }>()); - readonly onExit = this._onExit.event; - - private _process: ChildProcess | null = null; - private _hasExited: boolean = false; - - constructor( - public readonly id: string, - @ILogService private readonly _logService: IPartialLogService - ) { - super(); - } - - start(opts: IExtensionHostProcessOptions): { pid: number; } { - const sw = StopWatch.create(false); - this._process = fork( - FileAccess.asFileUri('bootstrap-fork', require).fsPath, - ['--type=extensionHost', '--skipWorkspaceStorageLock'], - mixin({ cwd: cwd() }, opts), - ); - const forkTime = sw.elapsed(); - const pid = this._process.pid; - - this._logService.info(`Starting extension host with pid ${pid} (fork() took ${forkTime} ms).`); - - const stdoutDecoder = new StringDecoder('utf-8'); - this._process.stdout?.on('data', (chunk) => { - const strChunk = typeof chunk === 'string' ? chunk : stdoutDecoder.write(chunk); - this._onStdout.fire(strChunk); - }); - - const stderrDecoder = new StringDecoder('utf-8'); - this._process.stderr?.on('data', (chunk) => { - const strChunk = typeof chunk === 'string' ? chunk : stderrDecoder.write(chunk); - this._onStderr.fire(strChunk); - }); - - this._process.on('message', msg => { - this._onMessage.fire(msg); - }); - - this._process.on('error', (err) => { - this._onError.fire({ error: transformErrorForSerialization(err) }); - }); - - this._process.on('exit', (code: number, signal: string) => { - this._hasExited = true; - this._onExit.fire({ pid, code, signal }); - }); - - return { pid }; - } - - enableInspectPort(): boolean { - if (!this._process) { - return false; - } - - this._logService.info(`Enabling inspect port on extension host with pid ${this._process.pid}.`); - - interface ProcessExt { - _debugProcess?(n: number): any; - } - - if (typeof (process)._debugProcess === 'function') { - // use (undocumented) _debugProcess feature of node - (process)._debugProcess!(this._process.pid); - return true; - } else if (!platform.isWindows) { - // use KILL USR1 on non-windows platforms (fallback) - this._process.kill('SIGUSR1'); - return true; - } else { - // not supported... - return false; - } - } - - kill(): void { - if (!this._process) { - return; - } - this._logService.info(`Killing extension host with pid ${this._process.pid}.`); - this._process.kill(); - } - - async waitForExit(maxWaitTimeMs: number): Promise { - if (!this._process) { - return; - } - const pid = this._process.pid; - this._logService.info(`Waiting for extension host with pid ${pid} to exit.`); - await Promise.race([Event.toPromise(this.onExit), timeout(maxWaitTimeMs)]); - - if (!this._hasExited) { - // looks like we timed out - this._logService.info(`Extension host with pid ${pid} did not exit within ${maxWaitTimeMs}ms.`); - this._process.kill(); - } - } -} - -export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter { - _serviceBrand: undefined; - - private static _lastId: number = 0; - - protected readonly _extHosts: Map; - - constructor( - @ILogService private readonly _logService: IPartialLogService - ) { - this._extHosts = new Map(); - } - - dispose(): void { - // Intentionally not killing the extension host processes - } - - private _getExtHost(id: string): ExtensionHostProcess { - const extHostProcess = this._extHosts.get(id); - if (!extHostProcess) { - throw new Error(`Unknown extension host!`); - } - return extHostProcess; - } - - onDynamicStdout(id: string): Event { - return this._getExtHost(id).onStdout; - } - - onDynamicStderr(id: string): Event { - return this._getExtHost(id).onStderr; - } - - onDynamicMessage(id: string): Event { - return this._getExtHost(id).onMessage; - } - - onDynamicError(id: string): Event<{ error: SerializedError; }> { - return this._getExtHost(id).onError; - } - - onDynamicExit(id: string): Event<{ code: number; signal: string; }> { - return this._getExtHost(id).onExit; - } - - async createExtensionHost(): Promise<{ id: string; }> { - const id = String(++ExtensionHostStarter._lastId); - const extHost = new ExtensionHostProcess(id, this._logService); - this._extHosts.set(id, extHost); - extHost.onExit(({ pid, code, signal }) => { - this._logService.info(`Extension host with pid ${pid} exited with code: ${code}, signal: ${signal}.`); - setTimeout(() => { - extHost.dispose(); - this._extHosts.delete(id); - }); - }); - return { id }; - } - - async start(id: string, opts: IExtensionHostProcessOptions): Promise<{ pid: number; }> { - return this._getExtHost(id).start(opts); - } - - async enableInspectPort(id: string): Promise { - const extHostProcess = this._extHosts.get(id); - if (!extHostProcess) { - return false; - } - return extHostProcess.enableInspectPort(); - } - - async kill(id: string): Promise { - const extHostProcess = this._extHosts.get(id); - if (!extHostProcess) { - // already gone! - return; - } - extHostProcess.kill(); - } - - async killAllNow(): Promise { - for (const [, extHost] of this._extHosts) { - extHost.kill(); - } - } - - async waitForAllExit(maxWaitTimeMs: number): Promise { - const exitPromises: Promise[] = []; - for (const [, extHost] of this._extHosts) { - exitPromises.push(extHost.waitForExit(maxWaitTimeMs)); - } - return Promises.settled(exitPromises).then(() => { }); - } -} - -registerSingleton(IExtensionHostStarter, ExtensionHostStarter, true); diff --git a/src/vs/platform/extensions/node/extensionHostStarterWorker.ts b/src/vs/platform/extensions/node/extensionHostStarterWorker.ts index 6bd329425ab..9ea3bd7857a 100644 --- a/src/vs/platform/extensions/node/extensionHostStarterWorker.ts +++ b/src/vs/platform/extensions/node/extensionHostStarterWorker.ts @@ -3,22 +3,238 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ExtensionHostStarter, IPartialLogService } from 'vs/platform/extensions/node/extensionHostStarter'; +import { SerializedError, transformErrorForSerialization } from 'vs/base/common/errors'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter'; +import { Emitter, Event } from 'vs/base/common/event'; +import { ChildProcess, fork } from 'child_process'; +import { FileAccess } from 'vs/base/common/network'; +import { StringDecoder } from 'string_decoder'; +import * as platform from 'vs/base/common/platform'; +import { mixin } from 'vs/base/common/objects'; +import { cwd } from 'vs/base/common/process'; +import { StopWatch } from 'vs/base/common/stopwatch'; +import { Promises, timeout } from 'vs/base/common/async'; export interface IExtensionHostStarterWorkerHost { logInfo(message: string): Promise; } +class ExtensionHostProcess extends Disposable { + + readonly _onStdout = this._register(new Emitter()); + readonly onStdout = this._onStdout.event; + + readonly _onStderr = this._register(new Emitter()); + readonly onStderr = this._onStderr.event; + + readonly _onMessage = this._register(new Emitter()); + readonly onMessage = this._onMessage.event; + + readonly _onError = this._register(new Emitter<{ error: SerializedError; }>()); + readonly onError = this._onError.event; + + readonly _onExit = this._register(new Emitter<{ pid: number; code: number; signal: string }>()); + readonly onExit = this._onExit.event; + + private _process: ChildProcess | null = null; + private _hasExited: boolean = false; + + constructor( + public readonly id: string, + private readonly _host: IExtensionHostStarterWorkerHost + ) { + super(); + } + + start(opts: IExtensionHostProcessOptions): { pid: number; } { + const sw = StopWatch.create(false); + this._process = fork( + FileAccess.asFileUri('bootstrap-fork', require).fsPath, + ['--type=extensionHost', '--skipWorkspaceStorageLock'], + mixin({ cwd: cwd() }, opts), + ); + const forkTime = sw.elapsed(); + const pid = this._process.pid; + + this._host.logInfo(`Starting extension host with pid ${pid} (fork() took ${forkTime} ms).`); + + const stdoutDecoder = new StringDecoder('utf-8'); + this._process.stdout?.on('data', (chunk) => { + const strChunk = typeof chunk === 'string' ? chunk : stdoutDecoder.write(chunk); + this._onStdout.fire(strChunk); + }); + + const stderrDecoder = new StringDecoder('utf-8'); + this._process.stderr?.on('data', (chunk) => { + const strChunk = typeof chunk === 'string' ? chunk : stderrDecoder.write(chunk); + this._onStderr.fire(strChunk); + }); + + this._process.on('message', msg => { + this._onMessage.fire(msg); + }); + + this._process.on('error', (err) => { + this._onError.fire({ error: transformErrorForSerialization(err) }); + }); + + this._process.on('exit', (code: number, signal: string) => { + this._hasExited = true; + this._onExit.fire({ pid, code, signal }); + }); + + return { pid }; + } + + enableInspectPort(): boolean { + if (!this._process) { + return false; + } + + this._host.logInfo(`Enabling inspect port on extension host with pid ${this._process.pid}.`); + + interface ProcessExt { + _debugProcess?(n: number): any; + } + + if (typeof (process)._debugProcess === 'function') { + // use (undocumented) _debugProcess feature of node + (process)._debugProcess!(this._process.pid); + return true; + } else if (!platform.isWindows) { + // use KILL USR1 on non-windows platforms (fallback) + this._process.kill('SIGUSR1'); + return true; + } else { + // not supported... + return false; + } + } + + kill(): void { + if (!this._process) { + return; + } + this._host.logInfo(`Killing extension host with pid ${this._process.pid}.`); + this._process.kill(); + } + + async waitForExit(maxWaitTimeMs: number): Promise { + if (!this._process) { + return; + } + const pid = this._process.pid; + this._host.logInfo(`Waiting for extension host with pid ${pid} to exit.`); + await Promise.race([Event.toPromise(this.onExit), timeout(maxWaitTimeMs)]); + + if (!this._hasExited) { + // looks like we timed out + this._host.logInfo(`Extension host with pid ${pid} did not exit within ${maxWaitTimeMs}ms.`); + this._process.kill(); + } + } +} + +export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter { + _serviceBrand: undefined; + + private static _lastId: number = 0; + + protected readonly _extHosts: Map; + + constructor( + private readonly _host: IExtensionHostStarterWorkerHost + ) { + this._extHosts = new Map(); + } + + dispose(): void { + // Intentionally not killing the extension host processes + } + + private _getExtHost(id: string): ExtensionHostProcess { + const extHostProcess = this._extHosts.get(id); + if (!extHostProcess) { + throw new Error(`Unknown extension host!`); + } + return extHostProcess; + } + + onDynamicStdout(id: string): Event { + return this._getExtHost(id).onStdout; + } + + onDynamicStderr(id: string): Event { + return this._getExtHost(id).onStderr; + } + + onDynamicMessage(id: string): Event { + return this._getExtHost(id).onMessage; + } + + onDynamicError(id: string): Event<{ error: SerializedError; }> { + return this._getExtHost(id).onError; + } + + onDynamicExit(id: string): Event<{ code: number; signal: string; }> { + return this._getExtHost(id).onExit; + } + + async createExtensionHost(): Promise<{ id: string; }> { + const id = String(++ExtensionHostStarter._lastId); + const extHost = new ExtensionHostProcess(id, this._host); + this._extHosts.set(id, extHost); + extHost.onExit(({ pid, code, signal }) => { + this._host.logInfo(`Extension host with pid ${pid} exited with code: ${code}, signal: ${signal}.`); + setTimeout(() => { + extHost.dispose(); + this._extHosts.delete(id); + }); + }); + return { id }; + } + + async start(id: string, opts: IExtensionHostProcessOptions): Promise<{ pid: number; }> { + return this._getExtHost(id).start(opts); + } + + async enableInspectPort(id: string): Promise { + const extHostProcess = this._extHosts.get(id); + if (!extHostProcess) { + return false; + } + return extHostProcess.enableInspectPort(); + } + + async kill(id: string): Promise { + const extHostProcess = this._extHosts.get(id); + if (!extHostProcess) { + // already gone! + return; + } + extHostProcess.kill(); + } + + async killAllNow(): Promise { + for (const [, extHost] of this._extHosts) { + extHost.kill(); + } + } + + async waitForAllExit(maxWaitTimeMs: number): Promise { + const exitPromises: Promise[] = []; + for (const [, extHost] of this._extHosts) { + exitPromises.push(extHost.waitForExit(maxWaitTimeMs)); + } + return Promises.settled(exitPromises).then(() => { }); + } +} + /** * The `create` function needs to be there by convention because * we are loaded via the `vs/base/common/worker/simpleWorker` utility. */ export function create(host: IExtensionHostStarterWorkerHost) { - const partialLogService: IPartialLogService = { - _serviceBrand: undefined, - info: (message: string): void => { - host.logInfo(message); - } - }; - return new ExtensionHostStarter(partialLogService); + return new ExtensionHostStarter(host); } diff --git a/src/vs/platform/files/common/watcher.ts b/src/vs/platform/files/common/watcher.ts index 4314712e66b..eb1d60df636 100644 --- a/src/vs/platform/files/common/watcher.ts +++ b/src/vs/platform/files/common/watcher.ts @@ -185,20 +185,20 @@ export function toFileChanges(changes: IDiskFileChange[]): IFileChange[] { })); } -export function normalizeFileChanges(changes: IDiskFileChange[]): IDiskFileChange[] { +export function coalesceEvents(changes: IDiskFileChange[]): IDiskFileChange[] { // Build deltas - const normalizer = new EventNormalizer(); + const coalescer = new EventCoalescer(); for (const event of changes) { - normalizer.processEvent(event); + coalescer.processEvent(event); } - return normalizer.normalize(); + return coalescer.coalesce(); } -class EventNormalizer { +class EventCoalescer { - private readonly normalized = new Set(); + private readonly coalesced = new Set(); private readonly mapPathToChange = new Map(); private toKey(event: IDiskFileChange): string { @@ -232,7 +232,7 @@ class EventNormalizer { // Ignore CREATE followed by DELETE in one go else if (currentChangeType === FileChangeType.ADDED && newChangeType === FileChangeType.DELETED) { this.mapPathToChange.delete(this.toKey(event)); - this.normalized.delete(existingEvent); + this.coalesced.delete(existingEvent); } // Flatten DELETE followed by CREATE into CHANGE @@ -255,12 +255,12 @@ class EventNormalizer { } if (keepEvent) { - this.normalized.add(event); + this.coalesced.add(event); this.mapPathToChange.set(this.toKey(event), event); } } - normalize(): IDiskFileChange[] { + coalesce(): IDiskFileChange[] { const addOrChangeEvents: IDiskFileChange[] = []; const deletedPaths: string[] = []; @@ -271,7 +271,7 @@ class EventNormalizer { // 1.) split ADD/CHANGE and DELETED events // 2.) sort short deleted paths to the top // 3.) for each DELETE, check if there is a deleted parent and ignore the event in that case - return Array.from(this.normalized).filter(e => { + return Array.from(this.coalesced).filter(e => { if (e.type !== FileChangeType.DELETED) { addOrChangeEvents.push(e); diff --git a/src/vs/platform/files/node/watcher/nodejs/watcherService.ts b/src/vs/platform/files/node/watcher/nodejs/watcherService.ts index 346cdb0b656..983b291e229 100644 --- a/src/vs/platform/files/node/watcher/nodejs/watcherService.ts +++ b/src/vs/platform/files/node/watcher/nodejs/watcherService.ts @@ -10,7 +10,7 @@ import { realpath } from 'vs/base/node/extpath'; import { SymlinkSupport } from 'vs/base/node/pfs'; import { CHANGE_BUFFER_DELAY, watchFile, watchFolder } from 'vs/base/node/watcher'; import { FileChangeType } from 'vs/platform/files/common/files'; -import { IDiskFileChange, ILogMessage, normalizeFileChanges } from 'vs/platform/files/common/watcher'; +import { IDiskFileChange, ILogMessage, coalesceEvents } from 'vs/platform/files/common/watcher'; export class FileWatcher extends Disposable { private isDisposed: boolean | undefined; @@ -95,19 +95,19 @@ export class FileWatcher extends Disposable { const fileChanges = this.fileChangesBuffer; this.fileChangesBuffer = []; - // Event normalization - const normalizedFileChanges = normalizeFileChanges(fileChanges); + // Event coalsecer + const coalescedFileChanges = coalesceEvents(fileChanges); // Logging if (this.verboseLogging) { - for (const event of normalizedFileChanges) { + for (const event of coalescedFileChanges) { this.onVerbose(`>> normalized ${event.type === FileChangeType.ADDED ? '[ADDED]' : event.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${event.path}`); } } // Fire - if (normalizedFileChanges.length > 0) { - this.onDidFilesChange(normalizedFileChanges); + if (coalescedFileChanges.length > 0) { + this.onDidFilesChange(coalescedFileChanges); } }); } diff --git a/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts b/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts index 0b7bd2a804a..7b415d8b902 100644 --- a/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts +++ b/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts @@ -18,7 +18,7 @@ import { isMacintosh } from 'vs/base/common/platform'; import { realcaseSync, realpathSync } from 'vs/base/node/extpath'; import { FileChangeType } from 'vs/platform/files/common/files'; import { IWatcherService } from 'vs/platform/files/node/watcher/nsfw/watcher'; -import { IDiskFileChange, ILogMessage, normalizeFileChanges, IWatchRequest } from 'vs/platform/files/common/watcher'; +import { IDiskFileChange, ILogMessage, coalesceEvents, IWatchRequest } from 'vs/platform/files/common/watcher'; import { watchFolder } from 'vs/base/node/watcher'; interface IWatcher extends IDisposable { @@ -192,7 +192,7 @@ export class NsfwWatcherService extends Disposable implements IWatcherService { undeliveredFileEvents = []; // Broadcast to clients normalized - const normalizedEvents = normalizeFileChanges(this.normalizeEvents(undeliveredFileEventsToEmit, request, realBasePathDiffers, realBasePathLength)); + const normalizedEvents = coalesceEvents(this.normalizeEvents(undeliveredFileEventsToEmit, request, realBasePathDiffers, realBasePathLength)); this.emitEvents(normalizedEvents); }, this.getOptions(watcher)).then(async nsfwWatcher => { diff --git a/src/vs/platform/files/node/watcher/parcel/parcelWatcherService.ts b/src/vs/platform/files/node/watcher/parcel/parcelWatcherService.ts index a731395f51b..26963631571 100644 --- a/src/vs/platform/files/node/watcher/parcel/parcelWatcherService.ts +++ b/src/vs/platform/files/node/watcher/parcel/parcelWatcherService.ts @@ -6,7 +6,7 @@ import * as parcelWatcher from '@parcel/watcher'; import { existsSync, unlinkSync } from 'fs'; import { tmpdir } from 'os'; -import { RunOnceScheduler } from 'vs/base/common/async'; +import { DeferredPromise, RunOnceScheduler } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter } from 'vs/base/common/event'; @@ -22,7 +22,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import { realcaseSync, realpathSync } from 'vs/base/node/extpath'; import { watchFolder } from 'vs/base/node/watcher'; import { FileChangeType } from 'vs/platform/files/common/files'; -import { IDiskFileChange, ILogMessage, normalizeFileChanges, IWatchRequest, IWatcherService } from 'vs/platform/files/common/watcher'; +import { IDiskFileChange, ILogMessage, coalesceEvents, IWatchRequest, IWatcherService } from 'vs/platform/files/common/watcher'; export interface IWatcher { @@ -244,15 +244,14 @@ export class ParcelWatcherService extends Disposable implements IWatcherService private startPolling(request: IWatchRequest, pollingInterval: number, restarts = 0): void { const cts = new CancellationTokenSource(); - let parcelWatcherPromiseResolve: () => void; - const instance = new Promise(resolve => parcelWatcherPromiseResolve = resolve); + const instance = new DeferredPromise(); const snapshotFile = join(tmpdir(), `vscode-watcher-snapshot-${generateUuid()}`); // Remember as watcher instance const watcher: IWatcher = { request, - ready: instance, + ready: instance.p, restarts, token: cts.token, stop: async () => { @@ -299,7 +298,7 @@ export class ParcelWatcherService extends Disposable implements IWatcherService // Signal we are ready now when the first snapshot was written if (counter === 1) { - parcelWatcherPromiseResolve(); + instance.complete(); } if (cts.token.isCancellationRequested) { @@ -315,19 +314,18 @@ export class ParcelWatcherService extends Disposable implements IWatcherService private startWatching(request: IWatchRequest, restarts = 0): void { const cts = new CancellationTokenSource(); - let parcelWatcherPromiseResolve: (watcher: parcelWatcher.AsyncSubscription | undefined) => void; - const instance = new Promise(resolve => parcelWatcherPromiseResolve = resolve); + const instance = new DeferredPromise(); // Remember as watcher instance const watcher: IWatcher = { request, - ready: instance, + ready: instance.p, restarts, token: cts.token, stop: async () => { cts.dispose(true); - const watcherInstance = await instance; + const watcherInstance = await instance.p; await watcherInstance?.unsubscribe(); } }; @@ -361,11 +359,11 @@ export class ParcelWatcherService extends Disposable implements IWatcherService }).then(parcelWatcher => { this.debug(`Started watching: '${realPath}' with backend '${ParcelWatcherService.PARCEL_WATCHER_BACKEND}' and native excludes '${ignore?.join(', ')}'`); - parcelWatcherPromiseResolve(parcelWatcher); + instance.complete(parcelWatcher); }).catch(error => { this.onUnexpectedError(error, watcher); - parcelWatcherPromiseResolve(undefined); + instance.complete(undefined); }); } @@ -377,14 +375,19 @@ export class ParcelWatcherService extends Disposable implements IWatcherService // Check for excludes const rawEvents = this.handleExcludes(parcelEvents, excludes); - // Normalize and detect root path deletes + // Normalize events: handle NFC normalization and symlinks const { events: normalizedEvents, rootDeleted } = this.normalizeEvents(rawEvents, watcher.request, realPathDiffers, realPathLength); - // Broadcast to clients coalesced - const coalescedEvents = normalizeFileChanges(normalizedEvents); - this.emitEvents(coalescedEvents); + // Coalesce events: merge events of same kind + const coalescedEvents = coalesceEvents(normalizedEvents); - // Handle root path delete if confirmed from coalseced events + // Filter events: check for specific events we want to exclude + const filteredEvents = this.filterEvents(coalescedEvents, watcher.request, rootDeleted); + + // Broadcast to clients + this.emitEvents(filteredEvents); + + // Handle root path delete if confirmed from coalesced events if (rootDeleted && coalescedEvents.some(event => event.path === watcher.request.path && event.type === FileChangeType.DELETED)) { this.onWatchedPathDeleted(watcher); } @@ -485,6 +488,25 @@ export class ParcelWatcherService extends Disposable implements IWatcherService return { events, rootDeleted }; } + private filterEvents(events: IDiskFileChange[], request: IWatchRequest, rootDeleted: boolean): IDiskFileChange[] { + if (!rootDeleted) { + return events; + } + + return events.filter(event => { + if (event.path === request.path && event.type === FileChangeType.DELETED) { + // Explicitly exclude changes to root if we have any + // to avoid VS Code closing all opened editors which + // can happen e.g. in case of network connectivity + // issues + // (https://github.com/microsoft/vscode/issues/136673) + return false; + } + + return true; + }); + } + private onWatchedPathDeleted(watcher: IWatcher): void { this.warn('Watcher shutdown because watched path got deleted', watcher); @@ -502,9 +524,6 @@ export class ParcelWatcherService extends Disposable implements IWatcherService // Stop watching that parent folder disposable.dispose(); - // Send a manual event given we know the root got added again - this.emitEvents([{ path: watcher.request.path, type: FileChangeType.ADDED }]); - // Restart the file watching this.restartWatching(watcher); } diff --git a/src/vs/platform/files/test/node/recursiveWatcher.integrationTest.ts b/src/vs/platform/files/test/node/recursiveWatcher.integrationTest.ts index ee26d0ec595..f415f587b33 100644 --- a/src/vs/platform/files/test/node/recursiveWatcher.integrationTest.ts +++ b/src/vs/platform/files/test/node/recursiveWatcher.integrationTest.ts @@ -122,6 +122,28 @@ flakySuite('Recursive Watcher (parcel)', () => { } }); }); + + // Unwind from the event call stack: we have seen crashes in Parcel + // when e.g. calling `unsubscribe` directly from the stack of a file + // change event + // Refs: https://github.com/microsoft/vscode/issues/137430 + await timeout(1); + } + + function awaitMessage(service: TestParcelWatcherService, type: 'trace' | 'warn' | 'error' | 'info' | 'debug'): Promise { + if (loggingEnabled) { + console.log(`Awaiting message of type ${type}`); + } + + // Await the message + return new Promise(resolve => { + const disposable = service.onDidLogMessage(msg => { + if (msg.type === type) { + disposable.dispose(); + resolve(); + } + }); + }); } test('basics', async function () { @@ -379,8 +401,6 @@ flakySuite('Recursive Watcher (parcel)', () => { changeFuture = awaitEvent(service, newTextFilePath, FileChangeType.ADDED); await Promises.writeFile(newTextFilePath, 'Hello World'); await changeFuture; - - return service.stop(); }); test('subsequent watch updates watchers (excludes)', async function () { @@ -392,8 +412,6 @@ flakySuite('Recursive Watcher (parcel)', () => { let changeFuture: Promise = awaitEvent(service, newTextFilePath, FileChangeType.ADDED); await Promises.writeFile(newTextFilePath, 'Hello World'); await changeFuture; - - return service.stop(); }); (isWindows /* windows: cannot create file symbolic link without elevated context */ ? test.skip : test)('symlink support (root)', async function () { @@ -447,22 +465,19 @@ flakySuite('Recursive Watcher (parcel)', () => { await service.watch([{ path: watchedPath, excludes: [] }]); - // Delete watched path - let changeFuture: Promise = awaitEvent(service, watchedPath, FileChangeType.DELETED); + // Delete watched path and await + const warnFuture = awaitMessage(service, 'warn'); await Promises.rm(watchedPath, RimRafMode.UNLINK); - await changeFuture; + await warnFuture; // Restore watched path - changeFuture = awaitEvent(service, watchedPath, FileChangeType.ADDED); await Promises.mkdir(watchedPath); - await changeFuture; - - await timeout(20); // restart is delayed + await timeout(1500); // restart is delayed await service.whenReady(); // Verify events come in again const newFilePath = join(watchedPath, 'newFile.txt'); - changeFuture = awaitEvent(service, newFilePath, FileChangeType.ADDED); + const changeFuture = awaitEvent(service, newFilePath, FileChangeType.ADDED); await Promises.writeFile(newFilePath, 'Hello World'); await changeFuture; }); diff --git a/src/vs/platform/files/test/node/watcherNormalizer.test.ts b/src/vs/platform/files/test/node/watcherCoalescer.test.ts similarity index 92% rename from src/vs/platform/files/test/node/watcherNormalizer.test.ts rename to src/vs/platform/files/test/node/watcherCoalescer.test.ts index 988be68c599..7853f6ce565 100644 --- a/src/vs/platform/files/test/node/watcherNormalizer.test.ts +++ b/src/vs/platform/files/test/node/watcherCoalescer.test.ts @@ -9,7 +9,7 @@ import { isLinux, isWindows } from 'vs/base/common/platform'; import { isEqual } from 'vs/base/common/resources'; import { URI as uri } from 'vs/base/common/uri'; import { FileChangesEvent, FileChangeType, IFileChange } from 'vs/platform/files/common/files'; -import { IDiskFileChange, normalizeFileChanges, toFileChanges } from 'vs/platform/files/common/watcher'; +import { IDiskFileChange, coalesceEvents, toFileChanges } from 'vs/platform/files/common/watcher'; class TestFileWatcher { private readonly _onDidFilesChange: Emitter<{ raw: IFileChange[], event: FileChangesEvent }>; @@ -28,12 +28,12 @@ class TestFileWatcher { private onRawFileEvents(events: IDiskFileChange[]): void { - // Normalize - let normalizedEvents = normalizeFileChanges(events); + // Coalesce + let coalescedEvents = coalesceEvents(events); // Emit through event emitter - if (normalizedEvents.length > 0) { - this._onDidFilesChange.fire({ raw: toFileChanges(normalizedEvents), event: this.toFileChangesEvent(normalizedEvents) }); + if (coalescedEvents.length > 0) { + this._onDidFilesChange.fire({ raw: toFileChanges(coalescedEvents), event: this.toFileChangesEvent(coalescedEvents) }); } } @@ -118,7 +118,7 @@ suite('Watcher Events Normalizer', () => { }); }); - test('event normalization: ignore CREATE followed by DELETE', done => { + test('event coalescer: ignore CREATE followed by DELETE', done => { const watch = new TestFileWatcher(); const created = uri.file('/users/data/src/related'); @@ -143,7 +143,7 @@ suite('Watcher Events Normalizer', () => { watch.report(raw); }); - test('event normalization: flatten DELETE followed by CREATE into CHANGE', done => { + test('event coalescer: flatten DELETE followed by CREATE into CHANGE', done => { const watch = new TestFileWatcher(); const deleted = uri.file('/users/data/src/related'); @@ -169,7 +169,7 @@ suite('Watcher Events Normalizer', () => { watch.report(raw); }); - test('event normalization: ignore UPDATE when CREATE received', done => { + test('event coalescer: ignore UPDATE when CREATE received', done => { const watch = new TestFileWatcher(); const created = uri.file('/users/data/src/related'); @@ -196,7 +196,7 @@ suite('Watcher Events Normalizer', () => { watch.report(raw); }); - test('event normalization: apply DELETE', done => { + test('event coalescer: apply DELETE', done => { const watch = new TestFileWatcher(); const updated = uri.file('/users/data/src/related'); @@ -225,7 +225,7 @@ suite('Watcher Events Normalizer', () => { watch.report(raw); }); - test('event normalization: track case renames', done => { + test('event coalescer: track case renames', done => { const watch = new TestFileWatcher(); const oldPath = uri.file('/users/data/src/added'); diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index 2fb91fc0a3b..1166a4ec7f3 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -212,7 +212,7 @@ export class IssueMainService implements ICommonIssueService { title: localize('issueReporter', "Issue Reporter"), zoomLevel: data.zoomLevel, alwaysOnTop: false - }); + }, 'issue-reporter'); // Store into config object URL issueReporterWindowConfigUrl.update({ @@ -267,7 +267,7 @@ export class IssueMainService implements ICommonIssueService { title: localize('processExplorer', "Process Explorer"), zoomLevel: data.zoomLevel, alwaysOnTop: true - }); + }, 'process-explorer'); // Store into config object URL processExplorerWindowConfigUrl.update({ @@ -301,7 +301,7 @@ export class IssueMainService implements ICommonIssueService { this.processExplorerWindow?.focus(); } - private createBrowserWindow(position: IWindowState, ipcObjectUrl: IIPCObjectUrl, options: IBrowserWindowOptions): BrowserWindow { + private createBrowserWindow(position: IWindowState, ipcObjectUrl: IIPCObjectUrl, options: IBrowserWindowOptions, windowKind: string): BrowserWindow { const window = new BrowserWindow({ fullscreen: false, skipTaskbar: true, @@ -316,7 +316,7 @@ export class IssueMainService implements ICommonIssueService { backgroundColor: options.backgroundColor || IssueMainService.DEFAULT_BACKGROUND_COLOR, webPreferences: { preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath, - additionalArguments: [`--vscode-window-config=${ipcObjectUrl.resource.toString()}`], + additionalArguments: [`--vscode-window-config=${ipcObjectUrl.resource.toString()}`, `--vscode-window-kind=${windowKind}`], v8CacheOptions: this.environmentMainService.useCodeCache ? 'bypassHeatCheck' : 'none', enableWebSQL: false, spellcheck: false, diff --git a/src/vs/platform/keybinding/test/common/keybindingLabels.test.ts b/src/vs/platform/keybinding/test/common/keybindingLabels.test.ts index 3e83d0f1478..833f82c709c 100644 --- a/src/vs/platform/keybinding/test/common/keybindingLabels.test.ts +++ b/src/vs/platform/keybinding/test/common/keybindingLabels.test.ts @@ -122,7 +122,7 @@ suite('KeybindingLabels', () => { assertAriaLabel(OperatingSystem.Windows, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KeyA, 'Control+Shift+Alt+Windows+A'); assertAriaLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KeyA, 'Control+Shift+Alt+Super+A'); - assertAriaLabel(OperatingSystem.Macintosh, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KeyA, 'Control+Shift+Alt+Command+A'); + assertAriaLabel(OperatingSystem.Macintosh, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KeyA, 'Control+Shift+Option+Command+A'); }); test('Electron Accelerator label', () => { diff --git a/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts b/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts index bfe95f931ef..fc9c0fe9e9a 100644 --- a/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts +++ b/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts @@ -20,13 +20,13 @@ import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platf export const ILifecycleMainService = createDecorator('lifecycleMainService'); -export interface IWindowLoadEvent { +export interface WindowLoadEvent { window: ICodeWindow; workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined; reason: LoadReason; } -export interface IWindowUnloadEvent { +export interface WindowUnloadEvent { window: ICodeWindow; reason: UnloadReason; veto(value: boolean | Promise): void; @@ -77,13 +77,13 @@ export interface ILifecycleMainService { * An event that fires when a window is loading. This can either be a window opening for the * first time or a window reloading or changing to another URL. */ - readonly onWillLoadWindow: Event; + readonly onWillLoadWindow: Event; /** * An event that fires before a window is about to unload. Listeners can veto this event to prevent * the window from unloading. */ - readonly onBeforeUnloadWindow: Event; + readonly onBeforeUnloadWindow: Event; /** * An event that fires before a window closes. This event is fired after any veto has been dealt @@ -91,13 +91,6 @@ export interface ILifecycleMainService { */ readonly onBeforeCloseWindow: Event; - /** - * An event that fires in the rare cases where `app.exit` is triggered - * and thus we Forcefully shutdown the application. No other lifecycle - * event handlers are triggered. - */ - readonly onWillKill: Event; - /** * Make a `ICodeWindow` known to the lifecycle main service. */ @@ -124,8 +117,16 @@ export interface ILifecycleMainService { quit(willRestart?: boolean): Promise; /** - * Forcefully shutdown the application. The only lifecycle event handler - * that is triggered is `onWillKill`. + * Forcefully shutdown the application and optionally set an exit code. + * + * This method should only be used in rare situations where it is important + * to set an exit code (e.g. running tests) or when the application is + * not in a healthy state and should terminate asap. + * + * This method does not fire the normal lifecycle events to the windows, + * that normally can be vetoed. Windows are destroyed without a chance + * of components to participate. The only lifecycle event handler that + * is triggered is `onWillShutdown` in the main process. */ kill(code?: number): Promise; @@ -168,18 +169,15 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe private readonly _onWillShutdown = this._register(new Emitter()); readonly onWillShutdown = this._onWillShutdown.event; - private readonly _onWillLoadWindow = this._register(new Emitter()); + private readonly _onWillLoadWindow = this._register(new Emitter()); readonly onWillLoadWindow = this._onWillLoadWindow.event; private readonly _onBeforeCloseWindow = this._register(new Emitter()); readonly onBeforeCloseWindow = this._onBeforeCloseWindow.event; - private readonly _onBeforeUnloadWindow = this._register(new Emitter()); + private readonly _onBeforeUnloadWindow = this._register(new Emitter()); readonly onBeforeUnloadWindow = this._onBeforeUnloadWindow.event; - private readonly _onWillKill = this._register(new Emitter()); - readonly onWillKill = this._onWillKill.event; - private _quitRequested = false; get quitRequested(): boolean { return this._quitRequested; } @@ -239,7 +237,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe // the onWillShutdown() event directly because there is no veto // to be expected. if (isMacintosh && this.windowCounter === 0) { - this.beginOnWillShutdown(); + this.fireOnWillShutdown(); } }; app.addListener('before-quit', beforeQuitListener); @@ -267,7 +265,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe e.preventDefault(); // Start shutdown sequence - const shutdownPromise = this.beginOnWillShutdown(); + const shutdownPromise = this.fireOnWillShutdown(); // Wait until shutdown is signaled to be complete shutdownPromise.finally(() => { @@ -285,7 +283,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe }); } - private beginOnWillShutdown(): Promise { + private fireOnWillShutdown(): Promise { if (this.pendingWillShutdownPromise) { return this.pendingWillShutdownPromise; // shutdown is already running } @@ -411,7 +409,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe // we are on macOS where it is perfectly fine to close the last window and // the application continues running (unless quit was actually requested) if (this.windowCounter === 0 && (!isMacintosh || this._quitRequested)) { - this.beginOnWillShutdown(); + this.fireOnWillShutdown(); } }); } @@ -600,17 +598,13 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe async kill(code?: number): Promise { this.logService.trace('Lifecycle#kill()'); - // Event - this._onWillKill.fire(); + // Give main process participants a chance to oderly shutdown + await this.fireOnWillShutdown(); - // The kill() method is only used in 2 situations: - // - when an instance fails to start at all - // - when extension tests run from CLI to report proper exit code - // // From extension tests we have seen issues where calling app.exit() - // with an opened window can lead to native crashes (Linux) when webviews - // are involved. As such, we should make sure to destroy any opened - // window before calling app.exit(). + // with an opened window can lead to native crashes (Linux). As such, + // we should make sure to destroy any opened window before calling + // `app.exit()`. // // Note: Electron implements a similar logic here: // https://github.com/electron/electron/blob/fe5318d753637c3903e23fc1ed1b263025887b6a/spec-main/window-helpers.ts#L5 diff --git a/src/vs/platform/log/node/spdlogLog.ts b/src/vs/platform/log/node/spdlogLog.ts index 9c7bc2b69a0..e72d4db4a36 100644 --- a/src/vs/platform/log/node/spdlogLog.ts +++ b/src/vs/platform/log/node/spdlogLog.ts @@ -23,12 +23,6 @@ async function createSpdLogLogger(name: string, logfilePath: string, filesize: n return null; } -export function createRotatingLogger(name: string, filename: string, filesize: number, filecount: number): Promise { - const _spdlog: typeof spdlog = require.__$__nodeRequire('spdlog'); - _spdlog.setFlushOn(LogLevel.Trace); - return _spdlog.createRotatingLogger(name, filename, filesize, filecount); -} - interface ILog { level: LogLevel; message: string; diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index 42522056520..bd4dc019b68 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -509,7 +509,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain } async writeElevated(windowId: number | undefined, source: URI, target: URI, options?: { unlock?: boolean }): Promise { - const sudoPrompt = await import('sudo-prompt'); + const sudoPrompt = await import('@vscode/sudo-prompt'); return new Promise((resolve, reject) => { const sudoCommand: string[] = [`"${this.cliPath}"`]; diff --git a/src/vs/platform/quickinput/browser/quickAccess.ts b/src/vs/platform/quickinput/browser/quickAccess.ts index a0d19728eac..cf29d5ae602 100644 --- a/src/vs/platform/quickinput/browser/quickAccess.ts +++ b/src/vs/platform/quickinput/browser/quickAccess.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { DeferredPromise } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { once } from 'vs/base/common/functional'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -111,10 +112,9 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon // Pick mode: setup a promise that can be resolved // with the selected items and prevent execution - let pickPromise: Promise | undefined = undefined; - let pickResolve: Function | undefined = undefined; + let pickPromise: DeferredPromise | undefined = undefined; if (pick) { - pickPromise = new Promise(resolve => pickResolve = resolve); + pickPromise = new DeferredPromise(); disposables.add(once(picker.onWillAccept)(e => { e.veto(); picker.hide(); @@ -143,7 +143,7 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon disposables.dispose(); // Resolve pick promise with selected items - pickResolve?.(picker.selectedItems); + pickPromise?.complete(picker.selectedItems.slice(0)); }); // Finally, show the picker. This is important because a provider @@ -153,7 +153,7 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon // Pick mode: return with promise if (pick) { - return pickPromise; + return pickPromise?.p; } } diff --git a/src/vs/platform/remote/browser/browserSocketFactory.ts b/src/vs/platform/remote/browser/browserSocketFactory.ts index 5197724fb1d..d9a5d9b26de 100644 --- a/src/vs/platform/remote/browser/browserSocketFactory.ts +++ b/src/vs/platform/remote/browser/browserSocketFactory.ts @@ -240,7 +240,8 @@ export class BrowserSocketFactory implements ISocketFactory { } connect(host: string, port: number, query: string, callback: IConnectCallback): void { - const socket = this._webSocketFactory.create(`ws://${/:/.test(host) ? `[${host}]` : host}:${port}/?${query}&skipWebSocketFrames=false`); + const webSocketSchema = (/^https:/.test(window.location.href) ? 'wss' : 'ws'); + const socket = this._webSocketFactory.create(`${webSocketSchema}://${/:/.test(host) ? `[${host}]` : host}:${port}/?${query}&skipWebSocketFrames=false`); const errorListener = socket.onError((err) => callback(err, undefined)); socket.onOpen(() => { errorListener.dispose(); diff --git a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts index 76bb55627fe..a50828715f2 100644 --- a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts +++ b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts @@ -63,7 +63,8 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot const pieces = authority.split(':'); return { authority: { authority, host: pieces[0], port: parseInt(pieces[1], 10), connectionToken } }; } - return { authority: { authority, host: authority, port: 80, connectionToken } }; + const port = (/^https:/.test(window.location.href) ? 443 : 80); + return { authority: { authority, host: authority, port: port, connectionToken } }; } _clearResolvedAuthority(authority: string): void { diff --git a/src/vs/platform/remote/common/remoteAuthorityResolver.ts b/src/vs/platform/remote/common/remoteAuthorityResolver.ts index ecf80672720..d5b97ca9121 100644 --- a/src/vs/platform/remote/common/remoteAuthorityResolver.ts +++ b/src/vs/platform/remote/common/remoteAuthorityResolver.ts @@ -19,6 +19,7 @@ export interface ResolvedAuthority { export interface ResolvedOptions { readonly extensionHostEnv?: { [key: string]: string | null }; readonly isTrusted?: boolean; + readonly authenticationSession?: { id: string, providerId: string }; } export interface TunnelDescription { diff --git a/src/vs/platform/sharedProcess/electron-browser/sharedProcessWorkerService.ts b/src/vs/platform/sharedProcess/electron-browser/sharedProcessWorkerService.ts index 1137f76e556..39d09c554ca 100644 --- a/src/vs/platform/sharedProcess/electron-browser/sharedProcessWorkerService.ts +++ b/src/vs/platform/sharedProcess/electron-browser/sharedProcessWorkerService.ts @@ -5,6 +5,7 @@ import { CrashReporterStartOptions, ipcRenderer } from 'electron'; import { join } from 'path'; +import { DeferredPromise } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -176,8 +177,7 @@ class SharedProcessWebWorker extends Disposable { } private doInit(): Promise { - let readyResolve: (result: Worker) => void; - const readyPromise = new Promise(resolve => readyResolve = resolve); + const readyPromise = new DeferredPromise(); const worker = new Worker('../../../base/worker/workerMain.js', { name: `Shared Process Worker (${this.type})` @@ -198,7 +198,7 @@ class SharedProcessWebWorker extends Disposable { // Lifecycle: Ready case SharedProcessWorkerMessages.Ready: - readyResolve(worker); + readyPromise.complete(worker); break; // Lifecycle: Ack @@ -250,7 +250,7 @@ class SharedProcessWebWorker extends Disposable { // First message triggers the load of the worker worker.postMessage('vs/platform/sharedProcess/electron-browser/sharedProcessWorkerMain'); - return readyPromise; + return readyPromise.p; } private async send(message: ISharedProcessToWorkerMessage, token: CancellationToken, port?: MessagePort): Promise { diff --git a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts index 36cd750dc0c..1f75cf92d18 100644 --- a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts @@ -218,7 +218,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { backgroundColor: this.themeMainService.getBackgroundColor(), webPreferences: { preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath, - additionalArguments: [`--vscode-window-config=${configObjectUrl.resource.toString()}`], + additionalArguments: [`--vscode-window-config=${configObjectUrl.resource.toString()}`, '--vscode-window-kind=shared-process'], v8CacheOptions: this.environmentMainService.useCodeCache ? 'bypassHeatCheck' : 'none', nodeIntegration: true, nodeIntegrationInWorker: true, @@ -227,8 +227,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { spellcheck: false, nativeWindowOpen: true, images: false, - webgl: false, - disableBlinkFeatures: 'Auxclick' // do NOT change, allows us to identify this window as shared-process in the process explorer + webgl: false } }); diff --git a/src/vs/platform/storage/electron-main/storageMain.ts b/src/vs/platform/storage/electron-main/storageMain.ts index d81f4d0f096..e82e328647e 100644 --- a/src/vs/platform/storage/electron-main/storageMain.ts +++ b/src/vs/platform/storage/electron-main/storageMain.ts @@ -6,14 +6,13 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { join } from 'vs/base/common/path'; -import { generateUuid } from 'vs/base/common/uuid'; import { Promises } from 'vs/base/node/pfs'; import { InMemoryStorageDatabase, IStorage, Storage, StorageHint } from 'vs/base/parts/storage/common/storage'; import { ISQLiteStorageDatabaseLoggingOptions, SQLiteStorageDatabase } from 'vs/base/parts/storage/node/storage'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; import { IS_NEW_KEY } from 'vs/platform/storage/common/storage'; -import { currentSessionDateStorageKey, firstSessionDateStorageKey, instanceStorageKey, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry'; +import { currentSessionDateStorageKey, firstSessionDateStorageKey, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry'; import { IEmptyWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; export interface IStorageMainOptions { @@ -214,12 +213,6 @@ export class GlobalStorageMain extends BaseStorageMain implements IStorageMain { private updateTelemetryState(storage: IStorage): void { - // Instance UUID (once) - const instanceId = storage.get(instanceStorageKey, undefined); - if (instanceId === undefined) { - storage.set(instanceStorageKey, generateUuid()); - } - // First session date (once) const firstSessionDate = storage.get(firstSessionDateStorageKey, undefined); if (firstSessionDate === undefined) { diff --git a/src/vs/platform/storage/test/electron-main/storageMainService.test.ts b/src/vs/platform/storage/test/electron-main/storageMainService.test.ts index 110a60eb77e..782140d4e99 100644 --- a/src/vs/platform/storage/test/electron-main/storageMainService.test.ts +++ b/src/vs/platform/storage/test/electron-main/storageMainService.test.ts @@ -17,7 +17,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IS_NEW_KEY } from 'vs/platform/storage/common/storage'; import { IStorageChangeEvent, IStorageMain, IStorageMainOptions } from 'vs/platform/storage/electron-main/storageMain'; import { StorageMainService } from 'vs/platform/storage/electron-main/storageMainService'; -import { currentSessionDateStorageKey, firstSessionDateStorageKey, instanceStorageKey } from 'vs/platform/telemetry/common/telemetry'; +import { currentSessionDateStorageKey, firstSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry'; import { ICodeWindow, UnloadReason } from 'vs/platform/windows/electron-main/windows'; suite('StorageMainService', function () { @@ -57,7 +57,6 @@ suite('StorageMainService', function () { onWillLoadWindow = Event.None; onBeforeCloseWindow = Event.None; onBeforeUnloadWindow = Event.None; - onWillKill = Event.None; wasRestarted = false; quitRequested = false; @@ -78,9 +77,7 @@ suite('StorageMainService', function () { // Telemetry: added after init if (isGlobal) { strictEqual(storage.items.size, 0); - strictEqual(storage.get(instanceStorageKey), undefined); await storage.init(); - strictEqual(typeof storage.get(instanceStorageKey), 'string'); strictEqual(typeof storage.get(firstSessionDateStorageKey), 'string'); strictEqual(typeof storage.get(currentSessionDateStorageKey), 'string'); } else { diff --git a/src/vs/platform/telemetry/common/telemetry.ts b/src/vs/platform/telemetry/common/telemetry.ts index 4f0dfa36049..be381cd790c 100644 --- a/src/vs/platform/telemetry/common/telemetry.ts +++ b/src/vs/platform/telemetry/common/telemetry.ts @@ -11,7 +11,6 @@ export const ITelemetryService = createDecorator('telemetrySe export interface ITelemetryInfo { sessionId: string; machineId: string; - instanceId: string; firstSessionDate: string; msftInternal?: boolean; } @@ -66,7 +65,6 @@ export interface ICustomEndpointTelemetryService { } // Keys -export const instanceStorageKey = 'telemetry.instanceId'; export const currentSessionDateStorageKey = 'telemetry.currentSessionDate'; export const firstSessionDateStorageKey = 'telemetry.firstSessionDate'; export const lastSessionDateStorageKey = 'telemetry.lastSessionDate'; diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index a3b17f5a821..c66501394a0 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -95,12 +95,11 @@ export class TelemetryService implements ITelemetryService { // well known properties let sessionId = values['sessionID']; - let instanceId = values['common.instanceId']; let machineId = values['common.machineId']; let firstSessionDate = values['common.firstSessionDate']; let msftInternal = values['common.msftInternal']; - return { sessionId, instanceId, machineId, firstSessionDate, msftInternal }; + return { sessionId, machineId, firstSessionDate, msftInternal }; } dispose(): void { diff --git a/src/vs/platform/telemetry/test/browser/telemetryService.test.ts b/src/vs/platform/telemetry/test/browser/telemetryService.test.ts index a930891c50e..66cccfc61d7 100644 --- a/src/vs/platform/telemetry/test/browser/telemetryService.test.ts +++ b/src/vs/platform/telemetry/test/browser/telemetryService.test.ts @@ -185,14 +185,12 @@ suite('TelemetryService', () => { appenders: [NullAppender], commonProperties: Promise.resolve({ sessionID: 'one', - ['common.instanceId']: 'two', ['common.machineId']: 'three', }) }, new TestConfigurationService()); return service.getTelemetryInfo().then(info => { assert.strictEqual(info.sessionId, 'one'); - assert.strictEqual(info.instanceId, 'two'); assert.strictEqual(info.machineId, 'three'); service.dispose(); diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 71c62368b09..026521405f7 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -91,6 +91,7 @@ export const enum TerminalSettingId { UnicodeVersion = 'terminal.integrated.unicodeVersion', ExperimentalLinkProvider = 'terminal.integrated.experimentalLinkProvider', LocalEchoLatencyThreshold = 'terminal.integrated.localEchoLatencyThreshold', + LocalEchoEnabled = 'terminal.integrated.localEchoEnabled', LocalEchoExcludePrograms = 'terminal.integrated.localEchoExcludePrograms', LocalEchoStyle = 'terminal.integrated.localEchoStyle', EnablePersistentSessions = 'terminal.integrated.enablePersistentSessions', diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index 2c3294935ab..1273db4f215 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -100,7 +100,8 @@ export class PtyService extends Disposable implements IPtyService { async serializeTerminalState(ids: number[]): Promise { const promises: Promise[] = []; for (const [persistentProcessId, persistentProcess] of this._ptys.entries()) { - if (ids.indexOf(persistentProcessId) !== -1) { + // Only serialize persistent processes that have had data written or performed a replay + if (persistentProcess.hasWrittenData && ids.indexOf(persistentProcessId) !== -1) { promises.push(Promises.withAsyncBody(async r => { r({ id: persistentProcessId, @@ -234,7 +235,7 @@ export class PtyService extends Disposable implements IPtyService { } async detachFromProcess(id: number): Promise { - this._throwIfNoPty(id).detach(); + return this._throwIfNoPty(id).detach(); } async reduceConnectionGraceTime(): Promise { @@ -342,9 +343,10 @@ export class PtyService extends Disposable implements IPtyService { private async _expandTerminalInstance(t: ITerminalInstanceLayoutInfoById): Promise> { try { - const persistentProcessId = this._revivedPtyIdMap.get(t.terminal)?.newId ?? t.terminal; + const revivedPtyId = this._revivedPtyIdMap.get(t.terminal)?.newId; + const persistentProcessId = revivedPtyId ?? t.terminal; const persistentProcess = this._throwIfNoPty(persistentProcessId); - const processDetails = persistentProcess && await this._buildProcessDetails(t.terminal, persistentProcess); + const processDetails = persistentProcess && await this._buildProcessDetails(t.terminal, persistentProcess, revivedPtyId !== undefined); return { terminal: { ...processDetails, id: persistentProcessId } ?? null, relativeSize: t.relativeSize @@ -359,8 +361,10 @@ export class PtyService extends Disposable implements IPtyService { } } - private async _buildProcessDetails(id: number, persistentProcess: PersistentTerminalProcess): Promise { - const [cwd, isOrphan] = await Promise.all([persistentProcess.getCwd(), persistentProcess.isOrphaned()]); + private async _buildProcessDetails(id: number, persistentProcess: PersistentTerminalProcess, wasRevived: boolean = false): Promise { + // If the process was just revived, don't do the orphan check as it will + // take some time + const [cwd, isOrphan] = await Promise.all([persistentProcess.getCwd(), wasRevived ? true : persistentProcess.isOrphaned()]); return { id, title: persistentProcess.title, @@ -399,6 +403,7 @@ export class PersistentTerminalProcess extends Disposable { private readonly _pendingCommands = new Map void; reject: (err: any) => void; }>(); private _isStarted: boolean = false; + private _hasWrittenData: boolean = false; private _orphanQuestionBarrier: AutoOpenBarrier | null; private _orphanQuestionReplyTime: number; @@ -429,6 +434,7 @@ export class PersistentTerminalProcess extends Disposable { get pid(): number { return this._pid; } get shellLaunchConfig(): IShellLaunchConfig { return this._terminalProcess.shellLaunchConfig; } + get hasWrittenData(): boolean { return this._hasWrittenData; } get title(): string { return this._title || this._terminalProcess.currentTitle; } get titleSource(): TitleEventSource { return this._titleSource; } get icon(): TerminalIcon | undefined { return this._icon; } @@ -568,6 +574,7 @@ export class PersistentTerminalProcess extends Disposable { return this._terminalProcess.shutdown(immediate); } input(data: string): void { + this._hasWrittenData = true; if (this._inReplay) { return; } @@ -608,6 +615,7 @@ export class PersistentTerminalProcess extends Disposable { } async triggerReplay(): Promise { + this._hasWrittenData = true; const ev = await this._serializer.generateReplayEvent(); let dataLength = 0; for (const e of ev.events) { diff --git a/src/vs/platform/terminal/node/terminalProfiles.ts b/src/vs/platform/terminal/node/terminalProfiles.ts index 5fa0a84441c..d9aab34bc5f 100644 --- a/src/vs/platform/terminal/node/terminalProfiles.ts +++ b/src/vs/platform/terminal/node/terminalProfiles.ts @@ -236,7 +236,7 @@ async function getWslProfiles(wslPath: string, defaultProfileName: string | unde const profiles: ITerminalProfile[] = []; const distroOutput = await new Promise((resolve, reject) => { // wsl.exe output is encoded in utf16le (ie. A -> 0x4100) - cp.exec('wsl.exe -l -q', { encoding: 'utf16le' }, (err, stdout) => { + cp.exec('wsl.exe -l -q', { encoding: 'utf16le', timeout: 1000 }, (err, stdout) => { if (err) { return reject('Problem occurred when getting wsl distros'); } diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 1734cb1249f..cb09db0b36c 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -225,7 +225,8 @@ export const inputBackground = registerColor('input.background', { dark: '#3C3C3 export const inputForeground = registerColor('input.foreground', { dark: foreground, light: foreground, hc: foreground }, nls.localize('inputBoxForeground', "Input box foreground.")); export const inputBorder = registerColor('input.border', { dark: null, light: null, hc: contrastBorder }, nls.localize('inputBoxBorder', "Input box border.")); export const inputActiveOptionBorder = registerColor('inputOption.activeBorder', { dark: '#007ACC00', light: '#007ACC00', hc: contrastBorder }, nls.localize('inputBoxActiveOptionBorder', "Border color of activated options in input fields.")); -export const inputActiveOptionBackground = registerColor('inputOption.activeBackground', { dark: transparent(focusBorder, 0.4), light: transparent(focusBorder, 0.2), hc: Color.transparent }, nls.localize('inputOption.activeBackground', "Background color of activated options in input fields.")); +export const inputActiveOptionHoverBackground = registerColor('inputOption.hoverBackground', { dark: '#5a5d5e80', light: '#b8b8b850', hc: null }, nls.localize('inputOption.hoverBackground', "Background color of activated options in input fields.")); +export const inputActiveOptionBackground = registerColor('inputOption.activeBackground', { dark: transparent(focusBorder, 0.4), light: transparent(focusBorder, 0.2), hc: Color.transparent }, nls.localize('inputOption.activeBackground', "Background hover color of options in input fields.")); export const inputActiveOptionForeground = registerColor('inputOption.activeForeground', { dark: Color.white, light: Color.black, hc: null }, nls.localize('inputOption.activeForeground', "Foreground color of activated options in input fields.")); export const inputPlaceholderForeground = registerColor('input.placeholderForeground', { light: transparent(foreground, 0.5), dark: transparent(foreground, 0.5), hc: transparent(foreground, 0.7) }, nls.localize('inputPlaceholderForeground', "Input box foreground color for placeholder text.")); diff --git a/src/vs/platform/theme/common/styler.ts b/src/vs/platform/theme/common/styler.ts index b4dac78d2a6..b69d95e8ad9 100644 --- a/src/vs/platform/theme/common/styler.ts +++ b/src/vs/platform/theme/common/styler.ts @@ -6,7 +6,7 @@ import { Color } from 'vs/base/common/color'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IThemable, styleFn } from 'vs/base/common/styler'; -import { activeContrastBorder, badgeBackground, badgeForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, breadcrumbsFocusForeground, breadcrumbsForeground, buttonBackground, buttonBorder, buttonForeground, buttonHoverBackground, buttonSecondaryBackground, buttonSecondaryForeground, buttonSecondaryHoverBackground, ColorIdentifier, ColorTransform, ColorValue, contrastBorder, editorWidgetBackground, editorWidgetBorder, editorWidgetForeground, focusBorder, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, keybindingLabelBackground, keybindingLabelBorder, keybindingLabelBottomBorder, keybindingLabelForeground, listActiveSelectionBackground, listActiveSelectionForeground, listActiveSelectionIconForeground, listDropBackground, listFilterWidgetBackground, listFilterWidgetNoMatchesOutline, listFilterWidgetOutline, listFocusBackground, listFocusForeground, listFocusOutline, listHoverBackground, listHoverForeground, listInactiveFocusBackground, listInactiveFocusOutline, listInactiveSelectionBackground, listInactiveSelectionForeground, listInactiveSelectionIconForeground, menuBackground, menuBorder, menuForeground, menuSelectionBackground, menuSelectionBorder, menuSelectionForeground, menuSeparatorBackground, pickerGroupForeground, problemsErrorIconForeground, problemsInfoIconForeground, problemsWarningIconForeground, progressBarBackground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, resolveColorValue, selectBackground, selectBorder, selectForeground, selectListBackground, simpleCheckboxBackground, simpleCheckboxBorder, simpleCheckboxForeground, tableColumnsBorder, tableOddRowsBackgroundColor, textLinkForeground, treeIndentGuidesStroke, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; +import { activeContrastBorder, badgeBackground, badgeForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, breadcrumbsFocusForeground, breadcrumbsForeground, buttonBackground, buttonBorder, buttonForeground, buttonHoverBackground, buttonSecondaryBackground, buttonSecondaryForeground, buttonSecondaryHoverBackground, ColorIdentifier, ColorTransform, ColorValue, contrastBorder, editorWidgetBackground, editorWidgetBorder, editorWidgetForeground, focusBorder, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, keybindingLabelBackground, keybindingLabelBorder, keybindingLabelBottomBorder, keybindingLabelForeground, listActiveSelectionBackground, listActiveSelectionForeground, listActiveSelectionIconForeground, listDropBackground, listFilterWidgetBackground, listFilterWidgetNoMatchesOutline, listFilterWidgetOutline, listFocusBackground, listFocusForeground, listFocusOutline, listHoverBackground, listHoverForeground, listInactiveFocusBackground, listInactiveFocusOutline, listInactiveSelectionBackground, listInactiveSelectionForeground, listInactiveSelectionIconForeground, menuBackground, menuBorder, menuForeground, menuSelectionBackground, menuSelectionBorder, menuSelectionForeground, menuSeparatorBackground, pickerGroupForeground, problemsErrorIconForeground, problemsInfoIconForeground, problemsWarningIconForeground, progressBarBackground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, resolveColorValue, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, selectBackground, selectBorder, selectForeground, selectListBackground, simpleCheckboxBackground, simpleCheckboxBorder, simpleCheckboxForeground, tableColumnsBorder, tableOddRowsBackgroundColor, textLinkForeground, treeIndentGuidesStroke, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { ColorScheme } from 'vs/platform/theme/common/theme'; import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; @@ -314,7 +314,11 @@ export const defaultMenuStyles = { selectionForegroundColor: menuSelectionForeground, selectionBackgroundColor: menuSelectionBackground, selectionBorderColor: menuSelectionBorder, - separatorColor: menuSeparatorBackground + separatorColor: menuSeparatorBackground, + scrollbarShadow: scrollbarShadow, + scrollbarSliderBackground: scrollbarSliderBackground, + scrollbarSliderHoverBackground: scrollbarSliderHoverBackground, + scrollbarSliderActiveBackground: scrollbarSliderActiveBackground }; export function attachMenuStyler(widget: IThemable, themeService: IThemeService, style?: IMenuStyleOverrides): IDisposable { diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index ee9c65f4cfd..b74f64dc33b 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -22,7 +22,7 @@ export type TokenClassificationString = string; export const idPattern = '\\w+[-_\\w+]*'; export const typeAndModifierIdPattern = `^${idPattern}$`; -export const selectorPattern = `^(${idPattern}|\\*)(\\${CLASSIFIER_MODIFIER_SEPARATOR}${idPattern})*(\\${TOKEN_CLASSIFIER_LANGUAGE_SEPARATOR}${idPattern})?$`; +export const selectorPattern = `^(${idPattern}|\\*)(\\${CLASSIFIER_MODIFIER_SEPARATOR}${idPattern})*(${TOKEN_CLASSIFIER_LANGUAGE_SEPARATOR}${idPattern})?$`; export const fontStylePattern = '^(\\s*(italic|bold|underline))*\\s*$'; diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index d6159097603..47af2647f89 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -21,6 +21,7 @@ import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { FileChangesEvent, FileOperationError, FileOperationResult, IFileContent, IFileService } from 'vs/platform/files/common/files'; +import { ILogService } from 'vs/platform/log/common/log'; import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -835,7 +836,7 @@ export abstract class AbstractInitializer implements IUserDataInitializer { constructor( readonly resource: SyncResource, @IEnvironmentService protected readonly environmentService: IEnvironmentService, - @IUserDataSyncLogService protected readonly logService: IUserDataSyncLogService, + @ILogService protected readonly logService: ILogService, @IFileService protected readonly fileService: IFileService, @IUriIdentityService uriIdentityService: IUriIdentityService, ) { @@ -854,12 +855,6 @@ export abstract class AbstractInitializer implements IUserDataInitializer { return; } - const isPreviouslySynced = await this.fileService.exists(this.lastSyncResource); - if (isPreviouslySynced) { - this.logService.info('Remote content does not exist.', this.resource); - return; - } - try { await this.doInitialize({ ref, syncData }); } catch (error) { diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 1bdce0eb1b6..3e56cc3875a 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -18,6 +18,7 @@ import { IExtensionGalleryService, IExtensionManagementService, IGlobalExtension import { areSameExtensions, getExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; +import { ILogService } from 'vs/platform/log/common/log'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; @@ -506,7 +507,7 @@ export abstract class AbstractExtensionsInitializer extends AbstractInitializer @IIgnoredExtensionsManagementService private readonly ignoredExtensionsManagementService: IIgnoredExtensionsManagementService, @IFileService fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService, - @IUserDataSyncLogService logService: IUserDataSyncLogService, + @ILogService logService: ILogService, @IUriIdentityService uriIdentityService: IUriIdentityService, ) { super(SyncResource.Extensions, environmentService, logService, fileService, uriIdentityService); diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 8720d6f7f16..17b6c9540eb 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -570,16 +570,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic const remoteAuthority = emptyWindowBackupInfo.remoteAuthority; const filesToOpenInWindow = isEqualAuthority(filesToOpen?.remoteAuthority, remoteAuthority) ? filesToOpen : undefined; - addUsedWindow(this.openInBrowserWindow({ - userEnv: openConfig.userEnv, - cli: openConfig.cli, - initialStartup: openConfig.initialStartup, - filesToOpen: filesToOpenInWindow, - remoteAuthority, - forceNewWindow: true, - forceNewTabbedWindow: openConfig.forceNewTabbedWindow, - emptyWindowBackupInfo - }), !!filesToOpenInWindow); + addUsedWindow(this.doOpenEmpty(openConfig, true, remoteAuthority, filesToOpenInWindow, emptyWindowBackupInfo), !!filesToOpenInWindow); openFolderInNewWindow = true; // any other folders to open must open in new window then }); @@ -605,7 +596,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } private doOpenFilesInExistingWindow(configuration: IOpenConfiguration, window: ICodeWindow, filesToOpen?: IFilesToOpen): ICodeWindow { - this.logService.trace('windowsManager#doOpenFilesInExistingWindow'); + this.logService.trace('windowsManager#doOpenFilesInExistingWindow', { filesToOpen }); window.focus(); // make sure window has focus @@ -621,7 +612,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } private doAddFoldersToExistingWindow(window: ICodeWindow, foldersToAdd: URI[]): ICodeWindow { - this.logService.trace('windowsManager#doAddFoldersToExistingWindow'); + this.logService.trace('windowsManager#doAddFoldersToExistingWindow', { foldersToAdd }); window.focus(); // make sure window has focus @@ -631,8 +622,11 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic return window; } - private doOpenEmpty(openConfig: IOpenConfiguration, forceNewWindow: boolean, remoteAuthority: string | undefined, filesToOpen: IFilesToOpen | undefined, windowToUse?: ICodeWindow): ICodeWindow { - if (!forceNewWindow && !windowToUse && typeof openConfig.contextWindowId === 'number') { + private doOpenEmpty(openConfig: IOpenConfiguration, forceNewWindow: boolean, remoteAuthority: string | undefined, filesToOpen: IFilesToOpen | undefined, emptyWindowBackupInfo?: IEmptyWindowBackupInfo): ICodeWindow { + this.logService.trace('windowsManager#doOpenEmpty', { restore: !!emptyWindowBackupInfo, remoteAuthority, filesToOpen, forceNewWindow }); + + let windowToUse: ICodeWindow | undefined; + if (!forceNewWindow && typeof openConfig.contextWindowId === 'number') { windowToUse = this.getWindowById(openConfig.contextWindowId); // fix for https://github.com/microsoft/vscode/issues/97172 } @@ -644,11 +638,14 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic forceNewWindow, forceNewTabbedWindow: openConfig.forceNewTabbedWindow, filesToOpen, - windowToUse + windowToUse, + emptyWindowBackupInfo }); } private doOpenFolderOrWorkspace(openConfig: IOpenConfiguration, folderOrWorkspace: IWorkspacePathToOpen | ISingleFolderWorkspacePathToOpen, forceNewWindow: boolean, filesToOpen: IFilesToOpen | undefined, windowToUse?: ICodeWindow): ICodeWindow { + this.logService.trace('windowsManager#doOpenFolderOrWorkspace', { folderOrWorkspace, filesToOpen }); + if (!forceNewWindow && !windowToUse && typeof openConfig.contextWindowId === 'number') { windowToUse = this.getWindowById(openConfig.contextWindowId); // fix for https://github.com/microsoft/vscode/issues/49587 } diff --git a/src/vs/server/main.js b/src/vs/server/main.js index 7426c16dde7..d223052aa7a 100644 --- a/src/vs/server/main.js +++ b/src/vs/server/main.js @@ -13,7 +13,7 @@ perf.mark('code/server/start'); // @ts-ignore global.vscodeServerStartTime = performance.now(); -function start() { +async function start() { if (process.argv[2] === '--exec') { process.argv.splice(1, 2); require(process.argv[1]); @@ -24,14 +24,13 @@ function start() { // Do a quick parse to determine if a server or the cli needs to be started const parsedArgs = minimist(process.argv.slice(2), { - boolean: ['start-server', 'list-extensions', 'print-ip-address'], - string: ['install-extension', 'install-builtin-extension', 'uninstall-extension', 'locate-extension', 'socket-path', 'host', 'port'] + boolean: ['start-server', 'list-extensions', 'print-ip-address', 'help', 'version'], + string: ['install-extension', 'install-builtin-extension', 'uninstall-extension', 'locate-extension', 'socket-path', 'host', 'port', 'pick-port'] }); - const shouldSpawnCli = ( - !parsedArgs['start-server'] && - (!!parsedArgs['list-extensions'] || !!parsedArgs['install-extension'] || !!parsedArgs['install-builtin-extension'] || !!parsedArgs['uninstall-extension'] || !!parsedArgs['locate-extension']) - ); + const extensionCliArgs = ['list-extensions', 'install-extension', 'install-builtin-extension', 'uninstall-extension', 'locate-extension']; + + const shouldSpawnCli = parsedArgs.help || parsedArgs.version || !parsedArgs['start-server'] && extensionCliArgs.some(a => !!parsedArgs[a]); if (shouldSpawnCli) { loadCode().then((mod) => { @@ -87,7 +86,7 @@ function start() { const nodeListenOptions = ( parsedArgs['socket-path'] ? { path: parsedArgs['socket-path'] } - : { host: parsedArgs['host'], port: parsePort(parsedArgs['port']) } + : { host: parsedArgs['host'], port: await parsePort(parsedArgs['port'], parsedArgs['pick-port']) } ); server.listen(nodeListenOptions, async () => { const serverGreeting = product.serverGreeting.join('\n'); @@ -129,20 +128,84 @@ function start() { } /** + * If `--pick-port` and `--port` is specified, connect to that port. + * + * If not and a port range is specified through `--pick-port` + * then find a free port in that range. Throw error if no + * free port available in range. + * + * If only `--port` is provided then connect to that port. + * + * In absence of specified ports, connect to port 8000. * @param {string | undefined} strPort - * @returns {number} + * @param {string | undefined} strPickPort + * @returns {Promise} + * @throws */ -function parsePort(strPort) { - try { - if (strPort) { - return parseInt(strPort); +async function parsePort(strPort, strPickPort) { + let specificPort = -1; + + if (strPort) { + const port = parseInt(strPort, 10); + if (!isNaN(port)) { + specificPort = port; + } else { + console.log('Port is not a number, will default to 8000 if no pick-port is given.'); } - } catch (e) { - console.log('Port is not a number, using 8000 instead.'); } + + if (strPickPort) { + if (strPickPort.match(/^\d+-\d+$/)) { + const [start, end] = strPickPort.split('-').map(numStr => { return parseInt(numStr, 10); }); + + if (!isNaN(start) && !isNaN(end)) { + if (specificPort !== -1 && specificPort >= start && specificPort <= end) { + return specificPort; + } else { + return await findFreePort(start, start, end); + } + } else { + console.log('Port range are not numbers, using 8000 instead.'); + } + } else { + console.log(`Port range: "${strPickPort}" is not properly formatted, using 8000 instead.`); + } + } + + if (specificPort !== -1) { + return specificPort; + } + return 8000; } +/** + * Starting at the `start` port, look for a free port incrementing + * by 1 until `end` inclusive. If no port is found error is thrown. + * + * @param {number} start + * @param {number} port + * @param {number} end + * @returns {Promise} + * @throws + */ +async function findFreePort(start, port, end) { + const http = require('http'); + return new Promise((resolve, reject) => { + if (port > end) { + throw new Error(`Could not find free port in range: ${start}-${end}`); + } + + const server = http.createServer(); + server.listen(port, () => { + server.close(); + resolve(port); + }).on('error', () => { + resolve(findFreePort(start, port + 1, end)); + }); + }); +} + /** @returns { Promise } */ function loadCode() { return new Promise((resolve, reject) => { diff --git a/src/vs/server/remoteCli.ts b/src/vs/server/remoteCli.ts index 28dfc625974..5dd86c63814 100644 --- a/src/vs/server/remoteCli.ts +++ b/src/vs/server/remoteCli.ts @@ -70,6 +70,8 @@ const isSupportedForPipe = (optionId: keyof RemoteParsedArgs) => { case 'force': case 'show-versions': case 'category': + case 'verbose': + case 'remote': return true; default: return false; @@ -79,7 +81,7 @@ const isSupportedForPipe = (optionId: keyof RemoteParsedArgs) => { const cliPipe = process.env['VSCODE_IPC_HOOK_CLI'] as string; const cliCommand = process.env['VSCODE_CLIENT_COMMAND'] as string; const cliCommandCwd = process.env['VSCODE_CLIENT_COMMAND_CWD'] as string; -const remoteAuthority = process.env['VSCODE_CLI_AUTHORITY'] as string; +const cliRemoteAuthority = process.env['VSCODE_CLI_AUTHORITY'] as string; const cliStdInFilePath = process.env['VSCODE_STDIN_FILE_PATH'] as string; @@ -114,10 +116,12 @@ export function main(desc: ProductDescription, args: string[]): void { }; const parsedArgs = parseArgs(args, options, errorReporter); - const mapFileUri = remoteAuthority ? mapFileToRemoteUri : (uri: string) => uri; + const mapFileUri = cliRemoteAuthority ? mapFileToRemoteUri : (uri: string) => uri; + + const verbose = !!parsedArgs['verbose']; if (parsedArgs.help) { - console.log(buildHelpMessage(desc.productName, desc.executableName, desc.version, options, true)); + console.log(buildHelpMessage(desc.productName, desc.executableName, desc.version, options)); return; } if (parsedArgs.version) { @@ -126,25 +130,31 @@ export function main(desc: ProductDescription, args: string[]): void { } if (cliPipe) { if (parsedArgs['openExternal']) { - openInBrowser(parsedArgs['_']); + openInBrowser(parsedArgs['_'], verbose); return; } } + let remote: string | null | undefined = parsedArgs.remote; + if (remote === 'local' || remote === 'false' || remote === '') { + remote = null; + } - let folderURIs = (parsedArgs['folder-uri'] || []).map(mapFileUri); + const folderURIs = (parsedArgs['folder-uri'] || []).map(mapFileUri); parsedArgs['folder-uri'] = folderURIs; - let fileURIs = (parsedArgs['file-uri'] || []).map(mapFileUri); + const fileURIs = (parsedArgs['file-uri'] || []).map(mapFileUri); parsedArgs['file-uri'] = fileURIs; - let inputPaths = parsedArgs['_']; + const inputPaths = parsedArgs['_']; let hasReadStdinArg = false; for (let input of inputPaths) { if (input === '-') { hasReadStdinArg = true; } else { - translatePath(input, mapFileUri, folderURIs, fileURIs); + if (remote !== undefined) { + translatePath(input, mapFileUri, folderURIs, fileURIs); + } } } @@ -155,7 +165,7 @@ export function main(desc: ProductDescription, args: string[]): void { let stdinFilePath = cliStdInFilePath; if (!stdinFilePath) { stdinFilePath = getStdinFilePath(); - readFromStdin(stdinFilePath, !!parsedArgs.verbose); // throws error if file can not be written + readFromStdin(stdinFilePath, verbose); // throws error if file can not be written } // Make sure to open tmp file @@ -186,10 +196,6 @@ export function main(desc: ProductDescription, args: string[]): void { return; } - if (remoteAuthority) { - parsedArgs['remote'] = remoteAuthority; - } - if (cliCommand) { if (parsedArgs['install-extension'] !== undefined || parsedArgs['uninstall-extension'] !== undefined || parsedArgs['list-extensions']) { const cmdLine: string[] = []; @@ -222,11 +228,14 @@ export function main(desc: ProductDescription, args: string[]): void { newCommandline.push(`--${key}=${val.toString()}`); } } + if (remote !== null) { + newCommandline.push(`--remote=${remote || cliRemoteAuthority}`); + } const ext = extname(cliCommand); if (ext === '.bat' || ext === '.cmd') { const processCwd = cliCommandCwd || cwd(); - if (parsedArgs['verbose']) { + if (verbose) { console.log(`Invoking: cmd.exe /C ${cliCommand} ${newCommandline.join(' ')} in ${processCwd}`); } _cp.spawn('cmd.exe', ['/C', cliCommand, ...newCommandline], { @@ -238,20 +247,16 @@ export function main(desc: ProductDescription, args: string[]): void { const env = { ...process.env, ELECTRON_RUN_AS_NODE: '1' }; newCommandline.unshift('--ms-enable-electron-run-as-node'); newCommandline.unshift('resources/app/out/cli.js'); - if (parsedArgs['verbose']) { - console.log(`Invoking: ${cliCommand} ${newCommandline.join(' ')} in ${cliCwd}`); + if (verbose) { + console.log(`Invoking: cd "${cliCwd}" && ELECTRON_RUN_AS_NODE=1 "${cliCommand}" "${newCommandline.join('" "')}"`); } _cp.spawn(cliCommand, newCommandline, { cwd: cliCwd, env, stdio: ['inherit'] }); } } else { - if (args.length === 0) { - console.log(buildHelpMessage(desc.productName, desc.executableName, desc.version, options, true)); - return; - } if (parsedArgs.status) { sendToPipe({ type: 'status' - }).then((res: string) => { + }, verbose).then((res: string) => { console.log(res); }); return; @@ -264,24 +269,19 @@ export function main(desc: ProductDescription, args: string[]): void { install: asExtensionIdOrVSIX(parsedArgs['install-extension']), uninstall: asExtensionIdOrVSIX(parsedArgs['uninstall-extension']), force: parsedArgs['force'] - }).then((res: string) => { + }, verbose).then((res: string) => { console.log(res); }); return; } - if (!fileURIs.length && !folderURIs.length) { - console.log('At least one file or folder must be provided.'); - return; - } - let waitMarkerFilePath: string | undefined = undefined; if (parsedArgs['wait']) { if (!fileURIs.length) { console.log('At least one file must be provided to wait for.'); return; } - waitMarkerFilePath = createWaitMarkerFile(parsedArgs.verbose); + waitMarkerFilePath = createWaitMarkerFile(verbose); } sendToPipe({ @@ -293,8 +293,9 @@ export function main(desc: ProductDescription, args: string[]): void { gotoLineMode: parsedArgs.goto, forceReuseWindow: parsedArgs['reuse-window'], forceNewWindow: parsedArgs['new-window'], - waitMarkerFilePath - }); + waitMarkerFilePath, + remoteAuthority: remote + }, verbose); if (waitMarkerFilePath) { waitForFileDeleted(waitMarkerFilePath); @@ -308,7 +309,7 @@ async function waitForFileDeleted(path: string) { } } -function openInBrowser(args: string[]) { +function openInBrowser(args: string[], verbose: boolean) { let uris: string[] = []; for (let location of args) { try { @@ -325,11 +326,14 @@ function openInBrowser(args: string[]) { sendToPipe({ type: 'openExternal', uris - }); + }, verbose); } } -function sendToPipe(args: PipeCommand): Promise { +function sendToPipe(args: PipeCommand, verbose: boolean): Promise { + if (verbose) { + console.log(JSON.stringify(args, null, ' ')); + } return new Promise(resolve => { const message = JSON.stringify(args); if (!cliPipe) { @@ -405,7 +409,7 @@ function translatePath(input: string, mapFileUri: (input: string) => string, fol } function mapFileToRemoteUri(uri: string): string { - return uri.replace(/^file:\/\//, 'vscode-remote://' + remoteAuthority); + return uri.replace(/^file:\/\//, 'vscode-remote://' + cliRemoteAuthority); } let [, , productName, version, commit, executableName, ...remainingArgs] = process.argv; diff --git a/src/vs/server/remoteExtensionHostAgent.ts b/src/vs/server/remoteExtensionHostAgent.ts index ab226cd31f4..1c45e1e21ed 100644 --- a/src/vs/server/remoteExtensionHostAgent.ts +++ b/src/vs/server/remoteExtensionHostAgent.ts @@ -53,7 +53,7 @@ args['extensions-dir'] = args['extensions-dir'] || join(REMOTE_DATA_FOLDER, 'ext * invoked by vs/server/main.js */ export function spawnCli() { - runCli(args, REMOTE_DATA_FOLDER); + runCli(args, REMOTE_DATA_FOLDER, serverOptions); } /** diff --git a/src/vs/server/remoteExtensionHostAgentCli.ts b/src/vs/server/remoteExtensionHostAgentCli.ts index f6b66dffed6..d375069f5f0 100644 --- a/src/vs/server/remoteExtensionHostAgentCli.ts +++ b/src/vs/server/remoteExtensionHostAgentCli.ts @@ -38,6 +38,8 @@ import { DownloadService } from 'vs/platform/download/common/downloadService'; import { IDownloadService } from 'vs/platform/download/common/download'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; +import { buildHelpMessage, buildVersionMessage, OptionDescriptions } from 'vs/platform/environment/node/argv'; +import { isWindows } from 'vs/base/common/platform'; class CliMain extends Disposable { @@ -135,7 +137,19 @@ function eventuallyExit(code: number): void { setTimeout(() => process.exit(code), 0); } -export async function run(args: ServerParsedArgs, REMOTE_DATA_FOLDER: string): Promise { +export async function run(args: ServerParsedArgs, REMOTE_DATA_FOLDER: string, optionDescriptions: OptionDescriptions): Promise { + if (args.help) { + const executable = `server${isWindows ? '.bat' : '.sh'}`; + console.log(buildHelpMessage(product.nameLong, executable, product.version, optionDescriptions, { noInputFiles: true, noPipe: true })); + return; + } + // Version Info + if (args.version) { + console.log(buildVersionMessage(product.version, product.commit)); + return; + } + + const cliMain = new CliMain(args, REMOTE_DATA_FOLDER); try { await cliMain.run(); diff --git a/src/vs/server/remoteExtensionHostAgentServer.ts b/src/vs/server/remoteExtensionHostAgentServer.ts index a54bbbe1d82..60f925fe191 100644 --- a/src/vs/server/remoteExtensionHostAgentServer.ts +++ b/src/vs/server/remoteExtensionHostAgentServer.ts @@ -322,8 +322,8 @@ export class RemoteExtensionHostAgentServer extends Disposable { const ptyService = instantiationService.createInstance( PtyHostService, { - GraceTime: ProtocolConstants.ReconnectionGraceTime, - ShortGraceTime: ProtocolConstants.ReconnectionShortGraceTime, + graceTime: ProtocolConstants.ReconnectionGraceTime, + shortGraceTime: ProtocolConstants.ReconnectionShortGraceTime, scrollback: configurationService.getValue(TerminalSettingId.PersistentSessionScrollback) ?? 100 } ); @@ -913,9 +913,13 @@ export class RemoteExtensionHostAgentServer extends Disposable { } function parseConnectionToken(args: ServerParsedArgs): { connectionToken: string; connectionTokenIsMandatory: boolean; } { + if (args['connectionToken']) { + console.warn(`The argument '--connectionToken' is deprecated, please use '--connection-token' instead`); + } + if (args['connection-secret']) { - if (args['connectionToken']) { - console.warn(`Please do not use the argument connectionToken at the same time as connection-secret.`); + if (args['connection-token']) { + console.warn(`Please do not use the argument '--connection-token' at the same time as '--connection-secret'.`); process.exit(1); } let rawConnectionToken = fs.readFileSync(args['connection-secret']).toString(); @@ -926,7 +930,7 @@ function parseConnectionToken(args: ServerParsedArgs): { connectionToken: string } return { connectionToken: rawConnectionToken, connectionTokenIsMandatory: true }; } else { - return { connectionToken: args['connectionToken'] || generateUuid(), connectionTokenIsMandatory: false }; + return { connectionToken: args['connection-token'] || args['connectionToken'] || generateUuid(), connectionTokenIsMandatory: false }; } } diff --git a/src/vs/server/serverEnvironmentService.ts b/src/vs/server/serverEnvironmentService.ts index 2e8a3ef9394..e993dbdcde3 100644 --- a/src/vs/server/serverEnvironmentService.ts +++ b/src/vs/server/serverEnvironmentService.ts @@ -11,7 +11,9 @@ import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/envi export const serverOptions: OptionDescriptions = { 'port': { type: 'string' }, - 'connectionToken': { type: 'string' }, + 'pick-port': { type: 'string' }, + 'connectionToken': { type: 'string' }, // deprecated in favor of `--connection-token` + 'connection-token': { type: 'string', description: nls.localize('connection-token', "A secret that must be included by the web client with all requests.") }, 'connection-secret': { type: 'string', description: nls.localize('connection-secret', "Path to file that contains the connection token. This will require that all incoming connections know the secret.") }, 'host': { type: 'string' }, 'socket-path': { type: 'string' }, @@ -53,12 +55,30 @@ export const serverOptions: OptionDescriptions = { 'log': { type: 'string' }, 'logsPath': { type: 'string' }, + 'help': OPTIONS['help'], + 'version': OPTIONS['version'], + _: OPTIONS['_'] }; export interface ServerParsedArgs { port?: string; + 'pick-port'?: string; + /** + * @deprecated use `connection-token` instead + */ connectionToken?: string; + /** + * A secret token that must be provided by the web client with all requests. + * Use only `[0-9A-Za-z\-]`. + * + * By default, a UUID will be generated every time the server starts up. + * + * If the server is running on a multi-user system, then consider + * using `--connection-secret` which has the advantage that the token cannot + * be seen by other users using `ps` or similar commands. + */ + 'connection-token'?: string; /** * A path to a filename which will be read on startup. * Consider placing this file in a folder readable only by the same user (a `chmod 0700` directory). @@ -112,6 +132,10 @@ export interface ServerParsedArgs { 'log'?: string; 'logsPath'?: string; + // server cli + help: boolean; + version: boolean; + _: string[]; } diff --git a/src/vs/workbench/api/browser/mainThreadCLICommands.ts b/src/vs/workbench/api/browser/mainThreadCLICommands.ts index 40d8f0a48d2..1fbb67106bc 100644 --- a/src/vs/workbench/api/browser/mainThreadCLICommands.ts +++ b/src/vs/workbench/api/browser/mainThreadCLICommands.ts @@ -31,8 +31,11 @@ CommandsRegistry.registerCommand('_remoteCLI.openExternal', function (accessor: return openerService.open(isString(uri) ? uri : URI.revive(uri), { openExternal: true, allowTunneling: true }); }); -CommandsRegistry.registerCommand('_remoteCLI.windowOpen', function (accessor: ServicesAccessor, toOpen: IWindowOpenable[], options?: IOpenWindowOptions) { +CommandsRegistry.registerCommand('_remoteCLI.windowOpen', function (accessor: ServicesAccessor, toOpen: IWindowOpenable[], options: IOpenWindowOptions) { const commandService = accessor.get(ICommandService); + if (!toOpen.length) { + return commandService.executeCommand('_files.newWindow', options); + } return commandService.executeCommand('_files.windowOpen', toOpen, options); }); diff --git a/src/vs/workbench/api/browser/mainThreadFileSystem.ts b/src/vs/workbench/api/browser/mainThreadFileSystem.ts index 152b6751fb1..042e2264f5f 100644 --- a/src/vs/workbench/api/browser/mainThreadFileSystem.ts +++ b/src/vs/workbench/api/browser/mainThreadFileSystem.ts @@ -149,6 +149,10 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { throw err; } + + $ensureActivation(scheme: string): Promise { + return this._fileService.activateProvider(scheme); + } } class RemoteFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileFolderCopyCapability { diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 5e6151d2875..3f14d2df3ef 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -384,27 +384,26 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- navigate type - $registerNavigateTypeSupport(handle: number): void { + $registerNavigateTypeSupport(handle: number, supportsResolve: boolean): void { let lastResultId: number | undefined; - this._registrations.set(handle, search.WorkspaceSymbolProviderRegistry.register({ - provideWorkspaceSymbols: (search: string, token: CancellationToken): Promise => { - return this._proxy.$provideWorkspaceSymbols(handle, search, token).then(result => { - if (lastResultId !== undefined) { - this._proxy.$releaseWorkspaceSymbols(handle, lastResultId); - } - lastResultId = result._id; - return MainThreadLanguageFeatures._reviveWorkspaceSymbolDto(result.symbols); - }); - }, - resolveWorkspaceSymbol: (item: search.IWorkspaceSymbol, token: CancellationToken): Promise => { - return this._proxy.$resolveWorkspaceSymbol(handle, item, token).then(i => { - if (i) { - return MainThreadLanguageFeatures._reviveWorkspaceSymbolDto(i); - } - return undefined; - }); + + const provider: search.IWorkspaceSymbolProvider = { + provideWorkspaceSymbols: async (search: string, token: CancellationToken): Promise => { + const result = await this._proxy.$provideWorkspaceSymbols(handle, search, token); + if (lastResultId !== undefined) { + this._proxy.$releaseWorkspaceSymbols(handle, lastResultId); + } + lastResultId = result._id; + return MainThreadLanguageFeatures._reviveWorkspaceSymbolDto(result.symbols); } - })); + }; + if (supportsResolve) { + provider.resolveWorkspaceSymbol = async (item: search.IWorkspaceSymbol, token: CancellationToken): Promise => { + const resolvedItem = await this._proxy.$resolveWorkspaceSymbol(handle, item, token); + return resolvedItem && MainThreadLanguageFeatures._reviveWorkspaceSymbolDto(resolvedItem); + }; + } + this._registrations.set(handle, search.WorkspaceSymbolProviderRegistry.register(provider)); } // --- rename @@ -679,7 +678,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha prepareCallHierarchy: async (document, position, token) => { const items = await this._proxy.$prepareCallHierarchy(handle, document.uri, position, token); - if (!items) { + if (!items || items.length === 0) { return undefined; } return { diff --git a/src/vs/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts b/src/vs/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts index ad86a0d4ea6..67ace55d95a 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts @@ -131,7 +131,7 @@ export class MainThreadNotebooksAndEditors { private _handleEditorAdd(editor: INotebookEditor): void { this._editorListeners.set(editor.getId(), combinedDisposable( editor.onDidChangeModel(() => this._updateState()), - editor.onDidFocusEditorWidget(() => this._updateState(editor)), + editor.onDidFocusWidget(() => this._updateState(editor)), )); this._updateState(); } diff --git a/src/vs/workbench/api/browser/mainThreadQuickOpen.ts b/src/vs/workbench/api/browser/mainThreadQuickOpen.ts index c5aec867363..9a47764e962 100644 --- a/src/vs/workbench/api/browser/mainThreadQuickOpen.ts +++ b/src/vs/workbench/api/browser/mainThreadQuickOpen.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IPickOptions, IInputOptions, IQuickInputService, IQuickInput, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { ExtHostContext, MainThreadQuickOpenShape, ExtHostQuickOpenShape, TransferQuickPickItem, MainContext, IExtHostContext, TransferQuickInput, TransferQuickInputButton, IInputBoxOptions } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostContext, MainThreadQuickOpenShape, ExtHostQuickOpenShape, TransferQuickPickItem, MainContext, IExtHostContext, TransferQuickInput, TransferQuickInputButton, IInputBoxOptions, TransferQuickPickItemOrSeparator } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { URI } from 'vs/base/common/uri'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -27,7 +27,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { private readonly _proxy: ExtHostQuickOpenShape; private readonly _quickInputService: IQuickInputService; private readonly _items: Record = {}; @@ -43,7 +43,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { } $show(instance: number, options: IPickOptions, token: CancellationToken): Promise { - const contents = new Promise((resolve, reject) => { + const contents = new Promise((resolve, reject) => { this._items[instance] = { resolve, reject }; }); @@ -73,7 +73,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { } } - $setItems(instance: number, items: TransferQuickPickItem[]): Promise { + $setItems(instance: number, items: TransferQuickPickItemOrSeparator[]): Promise { if (this._items[instance]) { this._items[instance].resolve(items); delete this._items[instance]; @@ -169,7 +169,11 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { } } else if (param === 'items') { handlesToItems.clear(); - params[param].forEach((item: TransferQuickPickItem) => { + params[param].forEach((item: TransferQuickPickItemOrSeparator) => { + if (item.type === 'separator') { + return; + } + if (item.buttons) { item.buttons = item.buttons.map((button: TransferQuickInputButton) => { if (button.iconPath) { diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index 1df23985f9c..747c2a4ddea 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -32,13 +32,14 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTreeViews); } - async $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean, canDragAndDrop: boolean }): Promise { + async $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean, dragAndDropMimeTypes: string[] | undefined }): Promise { this.logService.trace('MainThreadTreeViews#$registerTreeViewDataProvider', treeViewId, options); this.extensionService.whenInstalledExtensionsRegistered().then(() => { const dataProvider = new TreeViewDataProvider(treeViewId, this._proxy, this.notificationService); this._dataProviders.set(treeViewId, dataProvider); - const dndController = options.canDragAndDrop ? new TreeViewDragAndDropController(treeViewId, this._proxy) : undefined; + const dndController = options.dragAndDropMimeTypes + ? new TreeViewDragAndDropController(treeViewId, options.dragAndDropMimeTypes, this._proxy) : undefined; const viewer = this.getTreeView(treeViewId); if (viewer) { // Order is important here. The internal tree isn't created until the dataProvider is set. @@ -167,10 +168,11 @@ type TreeItemHandle = string; class TreeViewDragAndDropController implements ITreeViewDragAndDropController { constructor(private readonly treeViewId: string, + readonly supportedMimeTypes: string[], private readonly _proxy: ExtHostTreeViewsShape) { } - async onDrop(dataTransfer: ITreeDataTransfer, targetTreeItem: ITreeItem): Promise { - return this._proxy.$onDrop(this.treeViewId, await TreeDataTransferConverter.toTreeDataTransferDTO(dataTransfer), targetTreeItem.handle); + async onDrop(dataTransfer: ITreeDataTransfer, targetTreeItem: ITreeItem, sourceTreeId?: string, sourceTreeItemHandles?: string[]): Promise { + return this._proxy.$onDrop(this.treeViewId, await TreeDataTransferConverter.toTreeDataTransferDTO(dataTransfer), targetTreeItem.handle, sourceTreeId, sourceTreeItemHandles); } } diff --git a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts index d355e0876c4..5e00e96f690 100644 --- a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts +++ b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts @@ -17,7 +17,7 @@ import { WebviewIcons } from 'vs/workbench/contrib/webviewPanel/browser/webviewI import { ICreateWebViewShowOptions, IWebviewWorkbenchService } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService'; import { columnToEditorGroup, editorGroupToColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ACTIVE_GROUP, IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; /** @@ -154,11 +154,10 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc initData: extHostProtocol.IWebviewInitData, showOptions: extHostProtocol.WebviewPanelShowOptions, ): void { - const mainThreadShowOptions: ICreateWebViewShowOptions = Object.create(null); - if (showOptions) { - mainThreadShowOptions.preserveFocus = !!showOptions.preserveFocus; - mainThreadShowOptions.group = columnToEditorGroup(this._editorGroupService, showOptions.viewColumn); - } + const mainThreadShowOptions: ICreateWebViewShowOptions = showOptions ? { + preserveFocus: !!showOptions.preserveFocus, + group: columnToEditorGroup(this._editorGroupService, showOptions.viewColumn) + } : {}; const extension = reviveWebviewExtension(extensionData); @@ -198,10 +197,7 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc return; } - const targetGroup = this._editorGroupService.getGroup(columnToEditorGroup(this._editorGroupService, showOptions.viewColumn)) || this._editorGroupService.getGroup(webview.group || 0); - if (targetGroup) { - this._webviewWorkbenchService.revealWebview(webview, targetGroup, !!showOptions.preserveFocus); - } + this._webviewWorkbenchService.revealWebview(webview, showOptions.viewColumn ?? ACTIVE_GROUP, !!showOptions.preserveFocus); } public $registerSerializer(viewType: string, options: { serializeBuffersForPostMessage: boolean }): void { diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index c90faec496b..ff95e5d8b02 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -7,6 +7,7 @@ import { coalesce } from 'vs/base/common/arrays'; import { forEach } from 'vs/base/common/collections'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as resources from 'vs/base/common/resources'; +import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -310,12 +311,12 @@ class ViewsExtensionHandler implements IWorkbenchContribution { } for (let descriptor of viewsContainersDescriptors) { - if (typeof descriptor.id !== 'string') { - collector.error(localize('requireidstring', "property `{0}` is mandatory and must be of type `string`. Only alphanumeric characters, '_', and '-' are allowed.", 'id')); + if (typeof descriptor.id !== 'string' && isFalsyOrWhitespace(descriptor.id)) { + collector.error(localize('requireidstring', "property `{0}` is mandatory and must be of type `string` with non-empty value. Only alphanumeric characters, '_', and '-' are allowed.", 'id')); return false; } if (!(/^[a-z0-9_-]+$/i.test(descriptor.id))) { - collector.error(localize('requireidstring', "property `{0}` is mandatory and must be of type `string`. Only alphanumeric characters, '_', and '-' are allowed.", 'id')); + collector.error(localize('requireidstring', "property `{0}` is mandatory and must be of type `string` with non-empty value. Only alphanumeric characters, '_', and '-' are allowed.", 'id')); return false; } if (typeof descriptor.title !== 'string') { @@ -326,6 +327,10 @@ class ViewsExtensionHandler implements IWorkbenchContribution { collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'icon')); return false; } + if (isFalsyOrWhitespace(descriptor.title)) { + collector.warn(localize('requirenonemptystring', "property `{0}` is mandatory and must be of type `string` with non-empty value", 'title')); + return true; + } } return true; @@ -337,7 +342,8 @@ class ViewsExtensionHandler implements IWorkbenchContribution { const icon = themeIcon || resources.joinPath(extension.extensionLocation, descriptor.icon); const id = `workbench.view.extension.${descriptor.id}`; - const viewContainer = this.registerCustomViewContainer(id, descriptor.title, icon, order++, extension.identifier, location); + const title = descriptor.title || id; + const viewContainer = this.registerCustomViewContainer(id, title, icon, order++, extension.identifier, location); // Move those views that belongs to this container if (existingViewContainers.length) { @@ -405,8 +411,8 @@ class ViewsExtensionHandler implements IWorkbenchContribution { return; } - if (entry.key === 'remote' && !isProposedApiEnabled(extension.description)) { - collector.warn(localize('ViewContainerRequiresProposedAPI', "View container '{0}' requires 'enableProposedApi' turned on to be added to 'Remote'.", entry.key)); + if (entry.key === 'remote' && !isProposedApiEnabled(extension.description, 'contribViewsRemote')) { + collector.warn(localize('ViewContainerRequiresProposedAPI', "View container '{0}' requires 'enabledApiProposals: [\"contribViewsRemote\"]' to be added to 'Remote'.", entry.key)); return; } diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index 5d483b337aa..5f7b9e0cd93 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -8,7 +8,7 @@ import * as objects from 'vs/base/common/objects'; import { Registry } from 'vs/platform/registry/common/platform'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { IConfigurationNode, IConfigurationRegistry, Extensions, resourceLanguageSettingsSchemaId, validateProperty, ConfigurationScope, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationNode, IConfigurationRegistry, Extensions, resourceLanguageSettingsSchemaId, validateProperty, ConfigurationScope, OVERRIDE_PROPERTY_PATTERN, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { workspaceSettingsSchemaId, launchSchemaId, tasksSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { isObject } from 'vs/base/common/types'; @@ -110,7 +110,7 @@ const defaultConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint { const addedDefaultConfigurations = added.map>(extension => { const defaults: IStringDictionary = objects.deepClone(extension.value); for (const key of Object.keys(defaults)) { - if (!OVERRIDE_PROPERTY_PATTERN.test(key) || typeof defaults[key] !== 'object') { + if (!OVERRIDE_PROPERTY_REGEX.test(key) || typeof defaults[key] !== 'object') { extension.collector.warn(nls.localize('config.property.defaultConfiguration.warning', "Cannot register configuration defaults for '{0}'. Only defaults for language specific settings are supported.", key)); delete defaults[key]; } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 052b141e659..ed882131258 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -90,6 +90,7 @@ import { matchesScheme } from 'vs/platform/opener/common/opener'; import { ExtHostNotebookEditors } from 'vs/workbench/api/common/extHostNotebookEditors'; import { ExtHostNotebookDocuments } from 'vs/workbench/api/common/extHostNotebookDocuments'; import { ExtHostInteractive } from 'vs/workbench/api/common/extHostInteractive'; +import { combinedDisposable } from 'vs/base/common/lifecycle'; import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; export interface IExtensionApiFactory { @@ -218,7 +219,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I informOnce(); } if (typeof filter.exclusive === 'boolean') { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'documentFiltersExclusive'); } } return selector; @@ -227,14 +228,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const authentication: typeof vscode.authentication = { getSession(providerId: string, scopes: readonly string[], options?: vscode.AuthenticationGetSessionOptions) { - if (options?.forceNewSession) { - checkProposedApiEnabled(extension); - } return extHostAuthentication.getSession(extension, providerId, scopes, options as any); }, // TODO: remove this after GHPR and Codespaces move off of it async hasSession(providerId: string, scopes: readonly string[]) { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'authSession'); return !!(await extHostAuthentication.getSession(extension, providerId, scopes, { silent: true } as any)); }, get onDidChangeSessions(): Event { @@ -271,7 +269,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }, undefined, undefined, extension); }, registerDiffInformationCommand: (id: string, callback: (diff: vscode.LineChange[], ...args: any[]) => any, thisArg?: any): vscode.Disposable => { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'diffCommand'); return extHostCommands.registerCommand(true, id, async (...args: any[]): Promise => { const activeTextEditor = extHostDocumentsAndEditors.activeEditor(true); if (!activeTextEditor) { @@ -339,7 +337,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return getRemoteName(initData.remote.authority); }, get remoteAuthority() { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'resolvers'); return initData.remote.authority; }, get uiKind() { @@ -360,19 +358,19 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostTesting.createTestController(provider, label); }, createTestObserver() { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'testObserver'); return extHostTesting.createTestObserver(); }, runTests(provider) { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'testObserver'); return extHostTesting.runTests(provider); }, get onDidChangeTestResults() { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'testObserver'); return extHostTesting.onResultsChanged; }, get testResults() { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'testObserver'); return extHostTesting.results; }, }; @@ -484,7 +482,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostLanguageFeatures.registerCompletionItemProvider(extension, checkSelector(selector), provider, triggerCharacters); }, registerInlineCompletionItemProvider(selector: vscode.DocumentSelector, provider: vscode.InlineCompletionItemProvider): vscode.Disposable { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'inlineCompletions'); return extHostLanguageFeatures.registerInlineCompletionsProvider(extension, checkSelector(selector), provider); }, registerDocumentLinkProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentLinkProvider): vscode.Disposable { @@ -509,11 +507,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostLanguageFeatures.setLanguageConfiguration(extension, language, configuration); }, getTokenInformationAtPosition(doc: vscode.TextDocument, pos: vscode.Position) { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'tokenInformation'); return extHostLanguages.tokenAtPosition(doc, pos); }, registerInlayHintsProvider(selector: vscode.DocumentSelector, provider: vscode.InlayHintsProvider): vscode.Disposable { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'inlayHints'); return extHostLanguageFeatures.registerInlayHintsProvider(extension, selector, provider); }, createLanguageStatusItem(id: string, selector: vscode.DocumentSelector): vscode.LanguageStatusItem { @@ -574,14 +572,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostTerminalService.onDidChangeActiveTerminal(listener, thisArg, disposables); }, onDidChangeTerminalDimensions(listener, thisArg?, disposables?) { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'terminalDimensions'); return extHostTerminalService.onDidChangeTerminalDimensions(listener, thisArg, disposables); }, onDidChangeTerminalState(listener, thisArg?, disposables?) { return extHostTerminalService.onDidChangeTerminalState(listener, thisArg, disposables); }, onDidWriteTerminalData(listener, thisArg?, disposables?) { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'terminalDataWriteEvent'); return extHostTerminalService.onDidWriteTerminalData(listener, thisArg, disposables); }, get state() { @@ -600,7 +598,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return >extHostMessageService.showMessage(extension, Severity.Error, message, rest[0], >rest.slice(1)); }, showQuickPick(items: any, options?: vscode.QuickPickOptions, token?: vscode.CancellationToken): any { - return extHostQuickOpen.showQuickPick(items, isProposedApiEnabled(extension), options, token); + // TODO: remove this once quickPickSeparators has been finalized. + if (Array.isArray(items) && items.some((item) => item.kind !== undefined)) { + checkProposedApiEnabled(extension, 'quickPickSeparators'); + } + return extHostQuickOpen.showQuickPick(items, options, token); }, showWorkspaceFolderPick(options?: vscode.WorkspaceFolderPickOptions) { return extHostQuickOpen.showWorkspaceFolderPick(options); @@ -655,7 +657,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I createTerminal(nameOrOptions?: vscode.TerminalOptions | vscode.ExtensionTerminalOptions | string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal { if (typeof nameOrOptions === 'object') { if ('location' in nameOrOptions) { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'terminalLocation'); } if ('pty' in nameOrOptions) { return extHostTerminalService.createExtensionTerminal(nameOrOptions); @@ -689,7 +691,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostUrls.registerUriHandler(extension.identifier, handler); }, createQuickPick(): vscode.QuickPick { - return extHostQuickOpen.createQuickPick(extension.identifier, isProposedApiEnabled(extension)); + return extHostQuickOpen.createQuickPick(extension); }, createInputBox(): vscode.InputBox { return extHostQuickOpen.createInputBox(extension.identifier); @@ -708,55 +710,55 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostWebviewViews.registerWebviewViewProvider(extension, viewId, provider, options?.webviewOptions); }, get activeNotebookEditor(): vscode.NotebookEditor | undefined { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'notebookEditor'); return extHostNotebook.activeNotebookEditor; }, onDidChangeActiveNotebookEditor(listener, thisArgs?, disposables?) { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'notebookEditor'); return extHostNotebook.onDidChangeActiveNotebookEditor(listener, thisArgs, disposables); }, get visibleNotebookEditors() { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'notebookEditor'); return extHostNotebook.visibleNotebookEditors; }, get onDidChangeVisibleNotebookEditors() { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'notebookEditor'); return extHostNotebook.onDidChangeVisibleNotebookEditors; }, onDidChangeNotebookEditorSelection(listener, thisArgs?, disposables?) { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'notebookEditor'); return extHostNotebookEditors.onDidChangeNotebookEditorSelection(listener, thisArgs, disposables); }, onDidChangeNotebookEditorVisibleRanges(listener, thisArgs?, disposables?) { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'notebookEditor'); return extHostNotebookEditors.onDidChangeNotebookEditorVisibleRanges(listener, thisArgs, disposables); }, showNotebookDocument(uriOrDocument, options?) { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'notebookEditor'); return extHostNotebook.showNotebookDocument(uriOrDocument, options); }, registerExternalUriOpener(id: string, opener: vscode.ExternalUriOpener, metadata: vscode.ExternalUriOpenerMetadata) { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'externalUriOpener'); return extHostUriOpeners.registerExternalUriOpener(extension.identifier, id, opener, metadata); }, get tabs() { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'tabs'); return extHostEditorTabs.tabs; }, get activeTab() { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'tabs'); return extHostEditorTabs.activeTab; }, get onDidChangeTabs() { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'tabs'); return extHostEditorTabs.onDidChangeTabs; }, get onDidChangeActiveTab() { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'tabs'); return extHostEditorTabs.onDidChangeActiveTab; }, getInlineCompletionItemController(provider: vscode.InlineCompletionItemProvider): vscode.InlineCompletionController { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'inlineCompletions'); return InlineCompletionController.get(provider); } }; @@ -805,6 +807,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostWorkspace.findFiles(typeConverters.GlobPattern.from(include), typeConverters.GlobPattern.from(exclude), maxResults, extension.identifier, token); }, findTextInFiles: (query: vscode.TextSearchQuery, optionsOrCallback: vscode.FindTextInFilesOptions | ((result: vscode.TextSearchResult) => void), callbackOrToken?: vscode.CancellationToken | ((result: vscode.TextSearchResult) => void), token?: vscode.CancellationToken) => { + checkProposedApiEnabled(extension, 'findTextInFiles'); let options: vscode.FindTextInFilesOptions; let callback: (result: vscode.TextSearchResult) => void; @@ -891,11 +894,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostNotebook.onDidCloseNotebookDocument; }, registerNotebookSerializer(viewType: string, serializer: vscode.NotebookSerializer, options?: vscode.NotebookDocumentContentOptions, registration?: vscode.NotebookRegistrationData) { - return extHostNotebook.registerNotebookSerializer(extension, viewType, serializer, options, isProposedApiEnabled(extension) ? registration : undefined); + return extHostNotebook.registerNotebookSerializer(extension, viewType, serializer, options, isProposedApiEnabled(extension, 'notebookLiveShare') ? registration : undefined); }, registerNotebookContentProvider: (viewType: string, provider: vscode.NotebookContentProvider, options?: vscode.NotebookDocumentContentOptions, registration?: vscode.NotebookRegistrationData) => { - checkProposedApiEnabled(extension); - return extHostNotebook.registerNotebookContentProvider(extension, viewType, provider, options, isProposedApiEnabled(extension) ? registration : undefined); + checkProposedApiEnabled(extension, 'notebookContentProvider'); + return extHostNotebook.registerNotebookContentProvider(extension, viewType, provider, options, isProposedApiEnabled(extension, 'notebookLiveShare') ? registration : undefined); }, onDidChangeConfiguration: (listener: (_: any) => any, thisArgs?: any, disposables?: extHostTypes.Disposable[]) => { return configProvider.onDidChangeConfiguration(listener, thisArgs, disposables); @@ -914,25 +917,28 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostTask.registerTaskProvider(extension, type, provider); }, registerFileSystemProvider(scheme, provider, options) { - return extHostFileSystem.registerFileSystemProvider(extension, scheme, provider, options); + return combinedDisposable( + extHostFileSystem.registerFileSystemProvider(extension, scheme, provider, options), + extHostConsumerFileSystem.addFileSystemProvider(scheme, provider) + ); }, get fs() { return extHostConsumerFileSystem.value; }, registerFileSearchProvider: (scheme: string, provider: vscode.FileSearchProvider) => { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'fileSearchProvider'); return extHostSearch.registerFileSearchProvider(scheme, provider); }, registerTextSearchProvider: (scheme: string, provider: vscode.TextSearchProvider) => { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'textSearchProvider'); return extHostSearch.registerTextSearchProvider(scheme, provider); }, registerRemoteAuthorityResolver: (authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver) => { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'resolvers'); return extensionService.registerRemoteAuthorityResolver(authorityPrefix, resolver); }, registerResourceLabelFormatter: (formatter: vscode.ResourceLabelFormatter) => { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'resolvers'); return extHostLabelService.$registerResourceLabelFormatter(formatter); }, onDidCreateFiles: (listener, thisArg, disposables) => { @@ -954,7 +960,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostFileSystemEvent.getOnWillRenameFileEvent(extension)(listener, thisArg, disposables); }, openTunnel: (forward: vscode.TunnelOptions) => { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'resolvers'); return extHostTunnelService.openTunnel(extension, forward).then(value => { if (!value) { throw new Error('cannot open tunnel'); @@ -963,26 +969,26 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }); }, get tunnels() { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'resolvers'); return extHostTunnelService.getTunnels(); }, onDidChangeTunnels: (listener, thisArg?, disposables?) => { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'resolvers'); return extHostTunnelService.onDidChangeTunnels(listener, thisArg, disposables); }, registerPortAttributesProvider: (portSelector: { pid?: number, portRange?: [number, number], commandMatcher?: RegExp }, provider: vscode.PortAttributesProvider) => { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'portsAttributes'); return extHostTunnelService.registerPortsAttributesProvider(portSelector, provider); }, registerTimelineProvider: (scheme: string | string[], provider: vscode.TimelineProvider) => { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'timeline'); return extHostTimeline.registerTimelineProvider(scheme, provider, extension.identifier, extHostCommands.converter); }, get isTrusted() { return extHostWorkspace.trusted; }, requestWorkspaceTrust: (options?: vscode.WorkspaceTrustRequestOptions) => { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'workspaceTrust'); return extHostWorkspace.requestWorkspaceTrust(options); }, onDidGrantWorkspaceTrust: (listener, thisArgs?, disposables?) => { @@ -1095,44 +1101,44 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // namespace: notebook const notebooks: typeof vscode.notebooks = { createNotebookController(id: string, notebookType: string, label: string, handler?, rendererScripts?: vscode.NotebookRendererScript[]) { - return extHostNotebookKernels.createNotebookController(extension, id, notebookType, label, handler, isProposedApiEnabled(extension) ? rendererScripts : undefined); + return extHostNotebookKernels.createNotebookController(extension, id, notebookType, label, handler, isProposedApiEnabled(extension, 'notebookMessaging') ? rendererScripts : undefined); }, registerNotebookCellStatusBarItemProvider: (notebookType: string, provider: vscode.NotebookCellStatusBarItemProvider) => { return extHostNotebook.registerNotebookCellStatusBarItemProvider(extension, notebookType, provider); }, get onDidSaveNotebookDocument(): Event { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'notebookEditor'); return extHostNotebookDocuments.onDidSaveNotebookDocument; }, createNotebookEditorDecorationType(options: vscode.NotebookDecorationRenderOptions): vscode.NotebookEditorDecorationType { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'notebookEditorDecorationType'); return extHostNotebookEditors.createNotebookEditorDecorationType(options); }, createRendererMessaging(rendererId) { return extHostNotebookRenderers.createRendererMessaging(extension, rendererId); }, onDidChangeNotebookDocumentMetadata(listener, thisArgs?, disposables?) { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'notebookEditor'); return extHostNotebookDocuments.onDidChangeNotebookDocumentMetadata(listener, thisArgs, disposables); }, onDidChangeNotebookCells(listener, thisArgs?, disposables?) { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'notebookEditor'); return extHostNotebook.onDidChangeNotebookCells(listener, thisArgs, disposables); }, onDidChangeNotebookCellExecutionState(listener, thisArgs?, disposables?) { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'notebookCellExecutionState'); return extHostNotebook.onDidChangeNotebookCellExecutionState(listener, thisArgs, disposables); }, onDidChangeCellOutputs(listener, thisArgs?, disposables?) { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'notebookEditor'); return extHostNotebook.onDidChangeCellOutputs(listener, thisArgs, disposables); }, onDidChangeCellMetadata(listener, thisArgs?, disposables?) { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'notebookEditor'); return extHostNotebook.onDidChangeCellMetadata(listener, thisArgs, disposables); }, createConcatTextDocument(notebook, selector) { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'notebookConcatTextDocument'); return new ExtHostNotebookConcatDocument(extHostNotebook, extHostDocuments, notebook, selector); }, }; @@ -1299,6 +1305,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I TestTag: extHostTypes.TestTag, TestRunProfileKind: extHostTypes.TestRunProfileKind, TextSearchCompleteMessageType: TextSearchCompleteMessageType, + TreeDataTransfer: extHostTypes.TreeDataTransfer, + TreeDataTransferItem: extHostTypes.TreeDataTransferItem, CoveredCount: extHostTypes.CoveredCount, FileCoverage: extHostTypes.FileCoverage, StatementCoverage: extHostTypes.StatementCoverage, @@ -1306,6 +1314,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I FunctionCoverage: extHostTypes.FunctionCoverage, WorkspaceTrustState: extHostTypes.WorkspaceTrustState, LanguageStatusSeverity: extHostTypes.LanguageStatusSeverity, + QuickPickItemKind: extHostTypes.QuickPickItemKind, }; }; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index d935e9ab65a..c9f87013bb4 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -295,7 +295,7 @@ export interface MainThreadTextEditorsShape extends IDisposable { } export interface MainThreadTreeViewsShape extends IDisposable { - $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean, canDragAndDrop: boolean; }): Promise; + $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean, dragAndDropMimeTypes: string[] | undefined}): Promise; $refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem; }): Promise; $reveal(treeViewId: string, itemInfo: { item: ITreeItem, parentChain: ITreeItem[] } | undefined, options: IRevealOptions): Promise; $setMessage(treeViewId: string, message: string): void; @@ -405,7 +405,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerDocumentFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void; $registerRangeFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void; $registerOnTypeFormattingSupport(handle: number, selector: IDocumentFilterDto[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void; - $registerNavigateTypeSupport(handle: number): void; + $registerNavigateTypeSupport(handle: number, supportsResolve: boolean): void; $registerRenameSupport(handle: number, selector: IDocumentFilterDto[], supportsResolveInitialValues: boolean): void; $registerDocumentSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend, eventHandle: number | undefined): void; $emitDocumentSemanticTokensEvent(eventHandle: number): void; @@ -510,6 +510,7 @@ export interface MainThreadTerminalServiceShape extends IDisposable { $sendProcessExit(terminalId: number, exitCode: number | undefined): void; } +export type TransferQuickPickItemOrSeparator = TransferQuickPickItem | quickInput.IQuickPickSeparator; export interface TransferQuickPickItem extends quickInput.IQuickPickItem { handle: number; buttons?: TransferQuickInputButton[]; @@ -548,7 +549,7 @@ export interface TransferQuickPick extends BaseTransferQuickInput { buttons?: TransferQuickInputButton[]; - items?: TransferQuickPickItem[]; + items?: TransferQuickPickItemOrSeparator[]; activeItems?: number[]; @@ -594,7 +595,7 @@ export interface IInputBoxOptions { export interface MainThreadQuickOpenShape extends IDisposable { $show(instance: number, options: quickInput.IPickOptions, token: CancellationToken): Promise; - $setItems(instance: number, items: TransferQuickPickItem[]): Promise; + $setItems(instance: number, items: TransferQuickPickItemOrSeparator[]): Promise; $setError(instance: number, error: Error): Promise; $input(options: IInputBoxOptions | undefined, validateInput: boolean, token: CancellationToken): Promise; $createOrUpdate(params: TransferQuickInput): Promise; @@ -1015,6 +1016,8 @@ export interface MainThreadFileSystemShape extends IDisposable { $copy(resource: UriComponents, target: UriComponents, opts: files.FileOverwriteOptions): Promise; $mkdir(resource: UriComponents): Promise; $delete(resource: UriComponents, opts: files.FileDeleteOptions): Promise; + + $ensureActivation(scheme: string): Promise; } export interface MainThreadLabelServiceShape extends IDisposable { @@ -1270,7 +1273,7 @@ export interface ExtHostDocumentsAndEditorsShape { export interface ExtHostTreeViewsShape { $getChildren(treeViewId: string, treeItemHandle?: string): Promise; - $onDrop(treeViewId: string, treeDataTransfer: TreeDataTransferDTO, newParentTreeItemHandle: string): Promise; + $onDrop(destinationViewId: string, treeDataTransfer: TreeDataTransferDTO, newParentTreeItemHandle: string, sourceViewId?: string, sourceTreeItemHandles?: string[]): Promise; $setExpanded(treeViewId: string, treeItemHandle: string, expanded: boolean): void; $setSelection(treeViewId: string, treeItemHandles: string[]): void; $setVisible(treeViewId: string, visible: boolean): void; diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index 1a46b7661de..eeb38e33bf8 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -129,14 +129,8 @@ const newCommands: ApiCommand[] = [ new ApiCommand( 'vscode.executeWorkspaceSymbolProvider', '_executeWorkspaceSymbolProvider', 'Execute all workspace symbol providers.', [ApiCommandArgument.String.with('query', 'Search string')], - new ApiCommandResult<[search.IWorkspaceSymbolProvider, search.IWorkspaceSymbol[]][], types.SymbolInformation[]>('A promise that resolves to an array of SymbolInformation-instances.', value => { - const result: types.SymbolInformation[] = []; - if (Array.isArray(value)) { - for (let tuple of value) { - result.push(...tuple[1].map(typeConverters.WorkspaceSymbol.to)); - } - } - return result; + new ApiCommandResult('A promise that resolves to an array of SymbolInformation-instances.', value => { + return value.map(typeConverters.WorkspaceSymbol.to); }) ), // --- call hierarchy diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index 5c90db67dbf..e8033334ac6 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -11,7 +11,7 @@ import { ExtHostConfigurationShape, MainThreadConfigurationShape, IConfiguration import { ConfigurationTarget as ExtHostConfigurationTarget } from './extHostTypes'; import { ConfigurationTarget, IConfigurationChange, IConfigurationData, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { Configuration, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; -import { ConfigurationScope, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; +import { ConfigurationScope, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; import { isObject } from 'vs/base/common/types'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { Barrier } from 'vs/base/common/async'; @@ -297,7 +297,7 @@ export class ExtHostConfigProvider { } private _validateConfigurationAccess(key: string, overrides?: IConfigurationOverrides, extensionId?: ExtensionIdentifier): void { - const scope = OVERRIDE_PROPERTY_PATTERN.test(key) ? ConfigurationScope.RESOURCE : this._configurationScopes.get(key); + const scope = OVERRIDE_PROPERTY_REGEX.test(key) ? ConfigurationScope.RESOURCE : this._configurationScopes.get(key); const extensionIdText = extensionId ? `[${extensionId.value}] ` : ''; if (ConfigurationScope.RESOURCE === scope) { if (typeof overrides?.resource === 'undefined') { diff --git a/src/vs/workbench/api/common/extHostExtensionActivator.ts b/src/vs/workbench/api/common/extHostExtensionActivator.ts index 264e737fa89..207f34d520e 100644 --- a/src/vs/workbench/api/common/extHostExtensionActivator.ts +++ b/src/vs/workbench/api/common/extHostExtensionActivator.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import type * as vscode from 'vscode'; +import * as errors from 'vs/base/common/errors'; import { IDisposable } from 'vs/base/common/lifecycle'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @@ -169,7 +170,9 @@ export interface ExtensionActivationReason { type ActivationIdAndReason = { id: ExtensionIdentifier, reason: ExtensionActivationReason }; -export class ExtensionsActivator { +export class ExtensionsActivator implements IDisposable { + + private _isDisposed: boolean; private readonly _registry: ExtensionDescriptionRegistry; private readonly _resolvedExtensionsSet: Set; @@ -189,6 +192,7 @@ export class ExtensionsActivator { host: IExtensionsActivatorHost, @ILogService private readonly _logService: ILogService ) { + this._isDisposed = false; this._registry = registry; this._resolvedExtensionsSet = new Set(); resolvedExtensions.forEach((extensionId) => this._resolvedExtensionsSet.add(ExtensionIdentifier.toKey(extensionId))); @@ -200,6 +204,10 @@ export class ExtensionsActivator { this._alreadyActivatedEvents = Object.create(null); } + public dispose(): void { + this._isDisposed = true; + } + public isActivated(extensionId: ExtensionIdentifier): boolean { const extensionKey = ExtensionIdentifier.toKey(extensionId); @@ -406,6 +414,12 @@ export class ExtensionsActivator { error.stack = err.stack; } + if (this._isDisposed && errors.isPromiseCanceledError(err)) { + // It is expected for ongoing activations to fail if the extension host is going down + // So simply ignore and don't log canceled errors in this case + return new FailedExtension(err); + } + this._host.onExtensionActivationError( extensionId, error, diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 81bfcbc8280..20723bacc66 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -8,7 +8,7 @@ import * as path from 'vs/base/common/path'; import * as performance from 'vs/base/common/performance'; import { originalFSPath, joinPath, extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; import { asPromise, Barrier, timeout } from 'vs/base/common/async'; -import { dispose, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; +import { dispose, toDisposable, Disposable } from 'vs/base/common/lifecycle'; import { TernarySearchTree } from 'vs/base/common/map'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; @@ -110,8 +110,6 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme private _started: boolean; private _remoteConnectionData: IRemoteConnectionData | null; - private readonly _disposables: DisposableStore; - constructor( @IInstantiationService instaService: IInstantiationService, @IHostUtils hostUtils: IHostUtils, @@ -134,7 +132,6 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme this._logService = logService; this._extHostTunnelService = extHostTunnelService; this._extHostTerminalService = extHostTerminalService; - this._disposables = new DisposableStore(); this._mainThreadWorkspaceProxy = this._extHostContext.getProxy(MainContext.MainThreadWorkspace); this._mainThreadTelemetryProxy = this._extHostContext.getProxy(MainContext.MainThreadTelemetry); @@ -157,7 +154,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme const hostExtensions = new Set(); this._initData.hostExtensions.forEach((extensionId) => hostExtensions.add(ExtensionIdentifier.toKey(extensionId))); - this._activator = new ExtensionsActivator( + this._activator = this._register(new ExtensionsActivator( this._registry, this._initData.resolvedExtensions, this._initData.hostExtensions, @@ -176,7 +173,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme } }, this._logService - ); + )); this._extensionPathIndex = null; this._resolvers = Object.create(null); this._started = false; @@ -447,7 +444,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme return extension; }, get extensionRuntime() { - checkProposedApiEnabled(extensionDescription); + checkProposedApiEnabled(extensionDescription, 'extensionRuntime'); return that.extensionRuntime; }, get environmentVariableCollection() { return that._extHostTerminalService.getEnvironmentVariableCollection(extensionDescription); } @@ -523,7 +520,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme this._logService.error(err); }); - this._disposables.add(this._extHostWorkspace.onDidChangeWorkspace((e) => this._handleWorkspaceContainsEagerExtensions(e.added))); + this._register(this._extHostWorkspace.onDidChangeWorkspace((e) => this._handleWorkspaceContainsEagerExtensions(e.added))); const folders = this._extHostWorkspace.workspace ? this._extHostWorkspace.workspace.folders : []; const workspaceContainsActivation = this._handleWorkspaceContainsEagerExtensions(folders); const eagerExtensionsActivation = Promise.all([starActivation, workspaceContainsActivation]).then(() => { }); @@ -554,6 +551,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme const localWithRemote = !this._initData.remote.isRemote && !!this._initData.remote.authority; const host: IExtensionActivationHost = { + logService: this._logService, folders: folders.map(folder => folder.uri), forceUsingSearch: localWithRemote, exists: (uri) => this._hostUtils.exists(uri.fsPath), @@ -684,7 +682,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme } try { - this._disposables.add(await this._extHostTunnelService.setTunnelExtensionFunctions(resolver)); + this._register(await this._extHostTunnelService.setTunnelExtensionFunctions(resolver)); performance.mark(`code/extHost/willResolveAuthority/${authorityPrefix}`); const result = await resolver.resolve(remoteAuthority, { resolveAttempt }); performance.mark(`code/extHost/didResolveAuthorityOK/${authorityPrefix}`); @@ -698,7 +696,8 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme }; const options: ResolvedOptions = { extensionHostEnv: result.extensionHostEnv, - isTrusted: result.isTrusted + isTrusted: result.isTrusted, + authenticationSession: result.authenticationSessionForInitializingExtensions ? { id: result.authenticationSessionForInitializingExtensions.id, providerId: result.authenticationSessionForInitializingExtensions.providerId } : undefined }; return { diff --git a/src/vs/workbench/api/common/extHostFileSystemConsumer.ts b/src/vs/workbench/api/common/extHostFileSystemConsumer.ts index 46e331f4c83..cc8805a700b 100644 --- a/src/vs/workbench/api/common/extHostFileSystemConsumer.ts +++ b/src/vs/workbench/api/common/extHostFileSystemConsumer.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { MainContext } from './extHost.protocol'; +import { MainContext, MainThreadFileSystemShape } from './extHost.protocol'; import * as vscode from 'vscode'; import * as files from 'vs/platform/files/common/files'; import { FileSystemError } from 'vs/workbench/api/common/extHostTypes'; @@ -11,6 +11,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; export class ExtHostConsumerFileSystem { @@ -18,36 +19,110 @@ export class ExtHostConsumerFileSystem { readonly value: vscode.FileSystem; + private readonly _proxy: MainThreadFileSystemShape; + private readonly _fileSystemProvider = new Map(); + constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, @IExtHostFileSystemInfo fileSystemInfo: IExtHostFileSystemInfo, ) { - const proxy = extHostRpc.getProxy(MainContext.MainThreadFileSystem); + this._proxy = extHostRpc.getProxy(MainContext.MainThreadFileSystem); + const that = this; this.value = Object.freeze({ - stat(uri: vscode.Uri): Promise { - return proxy.$stat(uri).catch(ExtHostConsumerFileSystem._handleError); + async stat(uri: vscode.Uri): Promise { + try { + const provider = that._fileSystemProvider.get(uri.scheme); + if (!provider) { + return await that._proxy.$stat(uri); + } + // use shortcut + await that._proxy.$ensureActivation(uri.scheme); + const stat = await provider.stat(uri); + return { + type: stat.type, + ctime: stat.ctime, + mtime: stat.mtime, + size: stat.size, + permissions: stat.permissions + }; + } catch (err) { + ExtHostConsumerFileSystem._handleError(err); + } }, - readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> { - return proxy.$readdir(uri).catch(ExtHostConsumerFileSystem._handleError); + async readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> { + try { + const provider = that._fileSystemProvider.get(uri.scheme); + if (provider) { + // use shortcut + await that._proxy.$ensureActivation(uri.scheme); + return (await provider.readDirectory(uri)).slice(); // safe-copy + } else { + return await that._proxy.$readdir(uri); + } + } catch (err) { + return ExtHostConsumerFileSystem._handleError(err); + } }, - createDirectory(uri: vscode.Uri): Promise { - return proxy.$mkdir(uri).catch(ExtHostConsumerFileSystem._handleError); + async createDirectory(uri: vscode.Uri): Promise { + try { + // no shortcut: does mkdirp + return await that._proxy.$mkdir(uri); + } catch (err) { + return ExtHostConsumerFileSystem._handleError(err); + } }, async readFile(uri: vscode.Uri): Promise { - return proxy.$readFile(uri).then(buff => buff.buffer).catch(ExtHostConsumerFileSystem._handleError); + try { + const provider = that._fileSystemProvider.get(uri.scheme); + if (provider) { + // use shortcut + await that._proxy.$ensureActivation(uri.scheme); + return (await provider.readFile(uri)).slice(); // safe-copy + } else { + return that._proxy.$readFile(uri).then(buff => buff.buffer); + } + } catch (err) { + return ExtHostConsumerFileSystem._handleError(err); + } }, - writeFile(uri: vscode.Uri, content: Uint8Array): Promise { - return proxy.$writeFile(uri, VSBuffer.wrap(content)).catch(ExtHostConsumerFileSystem._handleError); + async writeFile(uri: vscode.Uri, content: Uint8Array): Promise { + try { + // no shortcut: does mkdirp + return await that._proxy.$writeFile(uri, VSBuffer.wrap(content)); + } catch (err) { + return ExtHostConsumerFileSystem._handleError(err); + } }, - delete(uri: vscode.Uri, options?: { recursive?: boolean; useTrash?: boolean; }): Promise { - return proxy.$delete(uri, { ...{ recursive: false, useTrash: false }, ...options }).catch(ExtHostConsumerFileSystem._handleError); + async delete(uri: vscode.Uri, options?: { recursive?: boolean; useTrash?: boolean; }): Promise { + try { + const provider = that._fileSystemProvider.get(uri.scheme); + if (provider) { + // use shortcut + await that._proxy.$ensureActivation(uri.scheme); + return await provider.delete(uri, { recursive: false, ...options }); + } else { + return await that._proxy.$delete(uri, { recursive: false, useTrash: false, ...options }); + } + } catch (err) { + return ExtHostConsumerFileSystem._handleError(err); + } }, - rename(oldUri: vscode.Uri, newUri: vscode.Uri, options?: { overwrite?: boolean; }): Promise { - return proxy.$rename(oldUri, newUri, { ...{ overwrite: false }, ...options }).catch(ExtHostConsumerFileSystem._handleError); + async rename(oldUri: vscode.Uri, newUri: vscode.Uri, options?: { overwrite?: boolean; }): Promise { + try { + // no shortcut: potentially involves different schemes, does mkdirp + return await that._proxy.$rename(oldUri, newUri, { ...{ overwrite: false }, ...options }); + } catch (err) { + return ExtHostConsumerFileSystem._handleError(err); + } }, - copy(source: vscode.Uri, destination: vscode.Uri, options?: { overwrite?: boolean; }): Promise { - return proxy.$copy(source, destination, { ...{ overwrite: false }, ...options }).catch(ExtHostConsumerFileSystem._handleError); + async copy(source: vscode.Uri, destination: vscode.Uri, options?: { overwrite?: boolean; }): Promise { + try { + // no shortcut: potentially involves different schemes, does mkdirp + return await that._proxy.$copy(source, destination, { ...{ overwrite: false }, ...options }); + } catch (err) { + return ExtHostConsumerFileSystem._handleError(err); + } }, isWritableFileSystem(scheme: string): boolean | undefined { const capabilities = fileSystemInfo.getCapabilities(scheme); @@ -60,6 +135,11 @@ export class ExtHostConsumerFileSystem { } private static _handleError(err: any): never { + // desired error type + if (err instanceof FileSystemError) { + throw err; + } + // generic error if (!(err instanceof Error)) { throw new FileSystemError(String(err)); @@ -82,6 +162,13 @@ export class ExtHostConsumerFileSystem { default: throw new FileSystemError(err.message, err.name as files.FileSystemProviderErrorCode); } } + + // --- + + addFileSystemProvider(scheme: string, provider: vscode.FileSystemProvider): IDisposable { + this._fileSystemProvider.set(scheme, provider); + return toDisposable(() => this._fileSystemProvider.delete(scheme)); + } } export interface IExtHostConsumerFileSystem extends ExtHostConsumerFileSystem { } diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 667289e83ad..7809c705277 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -1845,7 +1845,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF registerWorkspaceSymbolProvider(extension: IExtensionDescription, provider: vscode.WorkspaceSymbolProvider): vscode.Disposable { const handle = this._addNewAdapter(new NavigateTypeAdapter(provider, this._logService), extension); - this._proxy.$registerNavigateTypeSupport(handle); + this._proxy.$registerNavigateTypeSupport(handle, typeof provider.resolveWorkspaceSymbol === 'function'); return this._createDisposable(handle); } diff --git a/src/vs/workbench/api/common/extHostMessageService.ts b/src/vs/workbench/api/common/extHostMessageService.ts index 9f36d8dd3ac..be719110986 100644 --- a/src/vs/workbench/api/common/extHostMessageService.ts +++ b/src/vs/workbench/api/common/extHostMessageService.ts @@ -44,7 +44,7 @@ export class ExtHostMessageService { } if (options.useCustom) { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'resolvers'); } const commands: { title: string; isCloseAffordance: boolean; handle: number; }[] = []; diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts index 91b9099a90b..75895d19dcf 100644 --- a/src/vs/workbench/api/common/extHostNotebookKernels.ts +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -167,11 +167,11 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { _update(); }, get kind() { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'notebookControllerKind'); return data.kind ?? ''; }, set kind(value) { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'notebookControllerKind'); data.kind = value; _update(); }, @@ -234,11 +234,11 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { // --- ipc onDidReceiveMessage: onDidReceiveMessage.event, postMessage(message, editor) { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'notebookMessaging'); return that._proxy.$postMessage(handle, editor && that._extHostNotebook.getIdByEditor(editor), message); }, asWebviewUri(uri: URI) { - checkProposedApiEnabled(extension); + checkProposedApiEnabled(extension, 'notebookMessaging'); return asWebviewUri(uri, that._initData.remote); }, }; diff --git a/src/vs/workbench/api/common/extHostNotebookRenderers.ts b/src/vs/workbench/api/common/extHostNotebookRenderers.ts index c217759d9bd..3cc008e1010 100644 --- a/src/vs/workbench/api/common/extHostNotebookRenderers.ts +++ b/src/vs/workbench/api/common/extHostNotebookRenderers.ts @@ -32,7 +32,7 @@ export class ExtHostNotebookRenderers implements ExtHostNotebookRenderersShape { // In the stable API, the editor is given as an empty object, and this map // is used to maintain references. This can be removed after editor finalization. - const notebookEditorVisible = isProposedApiEnabled(manifest); + const notebookEditorVisible = isProposedApiEnabled(manifest, 'notebookEditor'); const notebookEditorAliases = new WeakMap<{}, vscode.NotebookEditor>(); const messaging: vscode.NotebookRendererMessaging = { diff --git a/src/vs/workbench/api/common/extHostQuickOpen.ts b/src/vs/workbench/api/common/extHostQuickOpen.ts index f5ec6a46edc..177d0649181 100644 --- a/src/vs/workbench/api/common/extHostQuickOpen.ts +++ b/src/vs/workbench/api/common/extHostQuickOpen.ts @@ -9,29 +9,30 @@ import { Emitter } from 'vs/base/common/event'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { IExtHostWorkspaceProvider } from 'vs/workbench/api/common/extHostWorkspace'; -import { InputBox, InputBoxOptions, QuickInput, QuickInputButton, QuickPick, QuickPickItem, QuickPickItemButtonEvent, QuickPickOptions, WorkspaceFolder, WorkspaceFolderPickOptions } from 'vscode'; -import { ExtHostQuickOpenShape, IMainContext, MainContext, TransferQuickPickItem, TransferQuickInput, TransferQuickInputButton } from './extHost.protocol'; +import type { InputBox, InputBoxOptions, QuickInput, QuickInputButton, QuickPick, QuickPickItem, QuickPickItemButtonEvent, QuickPickOptions, WorkspaceFolder, WorkspaceFolderPickOptions } from 'vscode'; +import { ExtHostQuickOpenShape, IMainContext, MainContext, TransferQuickInput, TransferQuickInputButton, TransferQuickPickItemOrSeparator } from './extHost.protocol'; import { URI } from 'vs/base/common/uri'; -import { ThemeIcon, QuickInputButtons } from 'vs/workbench/api/common/extHostTypes'; +import { ThemeIcon, QuickInputButtons, QuickPickItemKind } from 'vs/workbench/api/common/extHostTypes'; import { isPromiseCanceledError } from 'vs/base/common/errors'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { coalesce } from 'vs/base/common/arrays'; import Severity from 'vs/base/common/severity'; import { ThemeIcon as ThemeIconUtils } from 'vs/platform/theme/common/themeService'; +import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; export type Item = string | QuickPickItem; export interface ExtHostQuickOpen { - showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Promise, enableProposedApi: boolean, options: QuickPickOptions & { canPickMany: true; }, token?: CancellationToken): Promise; - showQuickPick(itemsOrItemsPromise: string[] | Promise, enableProposedApi: boolean, options?: QuickPickOptions, token?: CancellationToken): Promise; - showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Promise, enableProposedApi: boolean, options?: QuickPickOptions, token?: CancellationToken): Promise; - showQuickPick(itemsOrItemsPromise: Item[] | Promise, enableProposedApi: boolean, options?: QuickPickOptions, token?: CancellationToken): Promise; + showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Promise, options: QuickPickOptions & { canPickMany: true; }, token?: CancellationToken): Promise; + showQuickPick(itemsOrItemsPromise: string[] | Promise, options?: QuickPickOptions, token?: CancellationToken): Promise; + showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Promise, options?: QuickPickOptions, token?: CancellationToken): Promise; + showQuickPick(itemsOrItemsPromise: Item[] | Promise, options?: QuickPickOptions, token?: CancellationToken): Promise; showInput(options?: InputBoxOptions, token?: CancellationToken): Promise; showWorkspaceFolderPick(options?: WorkspaceFolderPickOptions, token?: CancellationToken): Promise - createQuickPick(extensionId: ExtensionIdentifier, enableProposedApi: boolean): QuickPick; + createQuickPick(extensionId: IExtensionDescription): QuickPick; createInputBox(extensionId: ExtensionIdentifier): InputBox; } @@ -56,10 +57,10 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx this._commands = commands; } - showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Promise, enableProposedApi: boolean, options: QuickPickOptions & { canPickMany: true; }, token?: CancellationToken): Promise; - showQuickPick(itemsOrItemsPromise: string[] | Promise, enableProposedApi: boolean, options?: QuickPickOptions, token?: CancellationToken): Promise; - showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Promise, enableProposedApi: boolean, options?: QuickPickOptions, token?: CancellationToken): Promise; - showQuickPick(itemsOrItemsPromise: Item[] | Promise, enableProposedApi: boolean, options?: QuickPickOptions, token: CancellationToken = CancellationToken.None): Promise { + showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Promise, options: QuickPickOptions & { canPickMany: true; }, token?: CancellationToken): Promise; + showQuickPick(itemsOrItemsPromise: string[] | Promise, options?: QuickPickOptions, token?: CancellationToken): Promise; + showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Promise, options?: QuickPickOptions, token?: CancellationToken): Promise; + showQuickPick(itemsOrItemsPromise: Item[] | Promise, options?: QuickPickOptions, token: CancellationToken = CancellationToken.None): Promise { // clear state from last invocation this._onDidSelectItem = undefined; @@ -87,33 +88,23 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx return itemsPromise.then(items => { - const pickItems: TransferQuickPickItem[] = []; + const pickItems: TransferQuickPickItemOrSeparator[] = []; for (let handle = 0; handle < items.length; handle++) { - const item = items[handle]; - let label: string; - let description: string | undefined; - let detail: string | undefined; - let picked: boolean | undefined; - let alwaysShow: boolean | undefined; - if (typeof item === 'string') { - label = item; + pickItems.push({ label: item, handle }); + } else if (item.kind === QuickPickItemKind.Separator) { + pickItems.push({ type: 'separator', label: item.label }); } else { - label = item.label; - description = item.description; - detail = item.detail; - picked = item.picked; - alwaysShow = item.alwaysShow; + pickItems.push({ + label: item.label, + description: item.description, + detail: item.detail, + picked: item.picked, + alwaysShow: item.alwaysShow, + handle + }); } - pickItems.push({ - label, - description, - handle, - detail, - picked, - alwaysShow - }); } // handle selection changes @@ -192,8 +183,8 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx // ---- QuickInput - createQuickPick(extensionId: ExtensionIdentifier, enableProposedApi: boolean): QuickPick { - const session: ExtHostQuickPick = new ExtHostQuickPick(extensionId, () => this._sessions.delete(session._id)); + createQuickPick(extension: IExtensionDescription): QuickPick { + const session: ExtHostQuickPick = new ExtHostQuickPick(extension, () => this._sessions.delete(session._id)); this._sessions.set(session._id, session); return session; } @@ -531,8 +522,9 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx private readonly _onDidChangeSelectionEmitter = new Emitter(); private readonly _onDidTriggerItemButtonEmitter = new Emitter>(); - constructor(extensionId: ExtensionIdentifier, onDispose: () => void) { - super(extensionId, onDispose); + // TODO: revert this change once quickPickSeparators has been finalized. + constructor(private readonly extension: IExtensionDescription, onDispose: () => void) { + super(extension.identifier, onDispose); this._disposables.push( this._onDidChangeActiveEmitter, this._onDidChangeSelectionEmitter, @@ -546,6 +538,10 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx } set items(items: T[]) { + if (items.some((item) => item.kind !== undefined)) { + checkProposedApiEnabled(this.extension, 'quickPickSeparators'); + } + this._items = items.slice(); this._handlesToItems.clear(); this._itemsToHandles.clear(); @@ -553,22 +549,33 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx this._handlesToItems.set(i, item); this._itemsToHandles.set(item, i); }); + + const pickItems: TransferQuickPickItemOrSeparator[] = []; + for (let handle = 0; handle < items.length; handle++) { + const item = items[handle]; + if (item.kind === QuickPickItemKind.Separator) { + pickItems.push({ type: 'separator', label: item.label }); + } else { + pickItems.push({ + handle, + label: item.label, + description: item.description, + detail: item.detail, + picked: item.picked, + alwaysShow: item.alwaysShow, + buttons: item.buttons?.map((button, i) => { + return { + ...getIconPathOrClass(button), + tooltip: button.tooltip, + handle: i + }; + }), + }); + } + } + this.update({ - items: items.map((item, i) => ({ - label: item.label, - description: item.description, - handle: i, - detail: item.detail, - picked: item.picked, - alwaysShow: item.alwaysShow, - buttons: item.buttons?.map((button, i) => { - return { - ...getIconPathOrClass(button), - tooltip: button.tooltip, - handle: i - }; - }), - })) + items: pickItems, }); } diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 1db3c75923c..574976beb09 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -230,13 +230,13 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox { private _validateInput: IValidateInput | undefined; get validateInput(): IValidateInput | undefined { - checkProposedApiEnabled(this._extension); + checkProposedApiEnabled(this._extension, 'scmValidation'); return this._validateInput; } set validateInput(fn: IValidateInput | undefined) { - checkProposedApiEnabled(this._extension); + checkProposedApiEnabled(this._extension, 'scmValidation'); if (fn && typeof fn !== 'function') { throw new Error(`[${this._extension.identifier.value}]: Invalid SCM input box validation function`); @@ -268,7 +268,7 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox { } showValidationMessage(message: string | vscode.MarkdownString, type: vscode.SourceControlInputBoxValidationType) { - checkProposedApiEnabled(this._extension); + checkProposedApiEnabled(this._extension, 'scmValidation'); this._proxy.$showValidationMessage(this._sourceControlHandle, message, type as any); } @@ -502,11 +502,11 @@ class ExtHostSourceControl implements vscode.SourceControl { private _actionButtonDisposables = new MutableDisposable(); private _actionButton: vscode.Command | undefined; get actionButton(): vscode.Command | undefined { - checkProposedApiEnabled(this._extension); + checkProposedApiEnabled(this._extension, 'scmActionButton'); return this._actionButton; } set actionButton(actionButton: vscode.Command | undefined) { - checkProposedApiEnabled(this._extension); + checkProposedApiEnabled(this._extension, 'scmActionButton'); this._actionButtonDisposables.value = new DisposableStore(); this._actionButton = actionButton; diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 6eb760ba348..47452b0060b 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -275,15 +275,11 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess { } input(data: string): void { - if (this._pty.handleInput) { - this._pty.handleInput(data); - } + this._pty.handleInput?.(data); } resize(cols: number, rows: number): void { - if (this._pty.setDimensions) { - this._pty.setDimensions({ columns: cols, rows }); - } + this._pty.setDimensions?.({ columns: cols, rows }); } async processBinary(data: string): Promise { @@ -314,28 +310,22 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess { startSendingEvents(initialDimensions: ITerminalDimensionsDto | undefined): void { // Attach the listeners this._pty.onDidWrite(e => this._onProcessData.fire(e)); - if (this._pty.onDidClose) { - this._pty.onDidClose((e: number | void = undefined) => { - this._onProcessExit.fire(e === void 0 ? undefined : e); - }); - } - if (this._pty.onDidOverrideDimensions) { - this._pty.onDidOverrideDimensions(e => { - if (e) { - this._onDidChangeProperty.fire({ type: ProcessPropertyType.OverrideDimensions, value: { cols: e.columns, rows: e.rows } }); - } - }); - } - if (this._pty.onDidChangeName) { - this._pty.onDidChangeName(title => { - this._onDidChangeProperty.fire({ type: ProcessPropertyType.Title, value: title }); - }); - } + this._pty.onDidClose?.((e: number | void = undefined) => { + this._onProcessExit.fire(e === void 0 ? undefined : e); + }); + this._pty.onDidOverrideDimensions?.(e => { + if (e) { + this._onDidChangeProperty.fire({ type: ProcessPropertyType.OverrideDimensions, value: { cols: e.columns, rows: e.rows } }); + } + }); + this._pty.onDidChangeName?.(title => { + this._onDidChangeProperty.fire({ type: ProcessPropertyType.Title, value: title }); + }); this._pty.open(initialDimensions ? initialDimensions : undefined); - if (this._pty.setDimensions && initialDimensions) { - this._pty.setDimensions(initialDimensions); + if (initialDimensions) { + this._pty.setDimensions?.(initialDimensions); } this._onProcessReady.fire({ pid: -1, cwd: '', capabilities: this._capabilities }); @@ -586,7 +576,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I protected _setupExtHostProcessListeners(id: number, p: ITerminalChildProcess): IDisposable { const disposables = new DisposableStore(); - disposables.add(p.onProcessReady((e: { pid: number, cwd: string }) => this._proxy.$sendProcessReady(id, e.pid, e.cwd))); + disposables.add(p.onProcessReady(e => this._proxy.$sendProcessReady(id, e.pid, e.cwd))); disposables.add(p.onDidChangeProperty(property => this._proxy.$sendProcessProperty(id, property))); // Buffer data events to reduce the amount of messages going to the renderer diff --git a/src/vs/workbench/api/common/extHostTextEditor.ts b/src/vs/workbench/api/common/extHostTextEditor.ts index 88a6ca5f703..6f9c4ed2452 100644 --- a/src/vs/workbench/api/common/extHostTextEditor.ts +++ b/src/vs/workbench/api/common/extHostTextEditor.ts @@ -226,7 +226,7 @@ export class ExtHostTextEditorOptions { // reflect the new tabSize value immediately this._tabSize = tabSize; } - this._warnOnError(this._proxy.$trySetOptions(this._id, { + this._warnOnError('setTabSize', this._proxy.$trySetOptions(this._id, { tabSize: tabSize })); } @@ -250,7 +250,7 @@ export class ExtHostTextEditorOptions { // reflect the new insertSpaces value immediately this._insertSpaces = insertSpaces; } - this._warnOnError(this._proxy.$trySetOptions(this._id, { + this._warnOnError('setInsertSpaces', this._proxy.$trySetOptions(this._id, { insertSpaces: insertSpaces })); } @@ -263,7 +263,7 @@ export class ExtHostTextEditorOptions { return; } this._cursorStyle = value; - this._warnOnError(this._proxy.$trySetOptions(this._id, { + this._warnOnError('setCursorStyle', this._proxy.$trySetOptions(this._id, { cursorStyle: value })); } @@ -276,7 +276,7 @@ export class ExtHostTextEditorOptions { return; } this._lineNumbers = value; - this._warnOnError(this._proxy.$trySetOptions(this._id, { + this._warnOnError('setLineNumbers', this._proxy.$trySetOptions(this._id, { lineNumbers: TypeConverters.TextEditorLineNumbersStyle.from(value) })); } @@ -341,12 +341,15 @@ export class ExtHostTextEditorOptions { } if (hasUpdate) { - this._warnOnError(this._proxy.$trySetOptions(this._id, bulkConfigurationUpdate)); + this._warnOnError('setOptions', this._proxy.$trySetOptions(this._id, bulkConfigurationUpdate)); } } - private _warnOnError(promise: Promise): void { - promise.catch(err => this._logService.warn(err)); + private _warnOnError(action: string, promise: Promise): void { + promise.catch(err => { + this._logService.warn(`ExtHostTextEditorOptions '${action}' failed:'`); + this._logService.warn(err); + }); } } diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index e4730e17821..09281009481 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ExtHostTreeViewsShape, MainThreadTreeViewsShape } from './extHost.protocol'; -import { ITreeItem, TreeViewItemHandleArg, ITreeItemLabel, IRevealOptions, TREE_ITEM_DATA_TRANSFER_TYPE } from 'vs/workbench/common/views'; +import { ITreeItem, TreeViewItemHandleArg, ITreeItemLabel, IRevealOptions } from 'vs/workbench/common/views'; import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { asPromise } from 'vs/base/common/async'; import { TreeItemCollapsibleState, ThemeIcon, MarkdownString as MarkdownStringType } from 'vs/workbench/api/common/extHostTypes'; @@ -85,8 +85,8 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { if (!options || !options.treeDataProvider) { throw new Error('Options with treeDataProvider is mandatory'); } - const canDragAndDrop = options.dragAndDropController !== undefined; - const registerPromise = this._proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany, canDragAndDrop: canDragAndDrop }); + const dragAndDropMimeTypes = options.dragAndDropController?.supportedMimeTypes; + const registerPromise = this._proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany, dragAndDropMimeTypes }); const treeView = this.createExtHostTreeView(viewId, options, extension); return { get onDidCollapseElement() { return treeView.onDidCollapseElement; }, @@ -129,22 +129,21 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { return treeView.getChildren(treeItemHandle); } - async $onDrop(treeViewId: string, treeDataTransferDTO: TreeDataTransferDTO, newParentItemHandle: string): Promise { - const treeView = this.treeViews.get(treeViewId); + async $onDrop(destinationViewId: string, treeDataTransferDTO: TreeDataTransferDTO, newParentItemHandle: string, sourceViewId?: string, sourceTreeItemHandles?: string[]): Promise { + const treeView = this.treeViews.get(destinationViewId); if (!treeView) { - return Promise.reject(new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId))); + return Promise.reject(new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', destinationViewId))); } const treeDataTransfer = TreeDataTransferConverter.toITreeDataTransfer(treeDataTransferDTO); - if (treeDataTransfer.items.has(TREE_ITEM_DATA_TRANSFER_TYPE)) { - const sourceHandles: string[] = JSON.parse(await treeDataTransfer.items.get(TREE_ITEM_DATA_TRANSFER_TYPE)!.asString()); - const sourceElements = sourceHandles.map(handle => treeView.getExtensionElement(handle)).filter(element => !!element); - if (sourceElements.length > 0) { - treeDataTransfer.items.set(TREE_ITEM_DATA_TRANSFER_TYPE, { - asString: async () => JSON.stringify(sourceElements) + if ((sourceViewId === destinationViewId) && sourceTreeItemHandles) { + const additionalTransferItems = await treeView.onWillDrop(sourceTreeItemHandles); + if (additionalTransferItems) { + additionalTransferItems.forEach((value, key) => { + if (value) { + treeDataTransfer.set(key, value); + } }); - } else { - treeDataTransfer.items.delete(TREE_ITEM_DATA_TRANSFER_TYPE); } } return treeView.onDrop(treeDataTransfer, newParentItemHandle); @@ -404,6 +403,21 @@ class ExtHostTreeView extends Disposable { } } + async onWillDrop(sourceTreeItemHandles: TreeItemHandle[]): Promise { + const extensionTreeItems: T[] = []; + for (const sourceHandle of sourceTreeItemHandles) { + const extensionItem = this.getExtensionElement(sourceHandle); + if (extensionItem) { + extensionTreeItems.push(extensionItem); + } + } + + if (!this.dndController?.onWillDrop || (extensionTreeItems.length === 0)) { + return; + } + return this.dndController.onWillDrop(extensionTreeItems); + } + async onDrop(treeDataTransfer: vscode.TreeDataTransfer, targetHandleOrNode: TreeItemHandle): Promise { const target = this.getExtensionElement(targetHandleOrNode); if (!target) { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 4f193198645..8c34ee60014 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2296,6 +2296,30 @@ export enum TreeItemCollapsibleState { Expanded = 2 } +@es5ClassCompat +export class TreeDataTransferItem { + async asString(): Promise { + return JSON.stringify(this._value); + } + + constructor(private readonly _value: any) { } +} + +@es5ClassCompat +export class TreeDataTransfer { + private readonly _items: Map = new Map(); + get(mimeType: string): T | undefined { + return this._items.get(mimeType); + } + set(mimeType: string, value: T): void { + this._items.set(mimeType, value); + } + forEach(callbackfn: (value: T, key: string) => void): void { + this._items.forEach(callbackfn); + } +} + + @es5ClassCompat export class ThemeIcon { @@ -2904,6 +2928,11 @@ export class QuickInputButtons { private constructor() { } } +export enum QuickPickItemKind { + Default = 1, + Separator = 2, +} + export enum ExtensionKind { UI = 1, Workspace = 2 diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 82999144a2e..6f7fb8389ca 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -17,14 +17,14 @@ import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { Iterable } from 'vs/base/common/iterator'; import { index } from 'vs/base/common/arrays'; import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { ApiProposalName } from 'vs/workbench/services/extensions/common/extensionsApiProposals'; interface IAPIMenu { readonly key: string; readonly id: MenuId; readonly description: string; - readonly proposed?: boolean; // defaults to false + readonly proposed?: ApiProposalName; readonly supportsSubmenus?: boolean; // defaults to true - readonly deprecationMessage?: string; } const apiMenus: IAPIMenu[] = [ @@ -85,17 +85,11 @@ const apiMenus: IAPIMenu[] = [ id: MenuId.DebugToolBar, description: localize('menus.debugToolBar', "The debug toolbar menu") }, - { - key: 'menuBar/file', - id: MenuId.MenubarFileMenu, - description: localize('menus.file', "The top level file menu"), - proposed: true - }, { key: 'menuBar/home', id: MenuId.MenubarHomeMenu, description: localize('menus.home', "The home indicator context menu (web only)"), - proposed: true, + proposed: 'contribMenuBarHome', supportsSubmenus: false }, { @@ -133,14 +127,6 @@ const apiMenus: IAPIMenu[] = [ id: MenuId.SCMChangeContext, description: localize('menus.changeTitle', "The Source Control inline change menu") }, - { - key: 'statusBar/windowIndicator', - id: MenuId.StatusBarWindowIndicatorMenu, - description: localize('menus.statusBarWindowIndicator', "The window indicator menu in the status bar"), - proposed: true, - supportsSubmenus: false, - deprecationMessage: localize('menus.statusBarWindowIndicator.deprecated', "Use menu 'statusBar/remoteIndicator' instead."), - }, { key: 'statusBar/remoteIndicator', id: MenuId.StatusBarRemoteIndicatorMenu, @@ -198,19 +184,19 @@ const apiMenus: IAPIMenu[] = [ key: 'notebook/cell/executePrimary', id: MenuId.NotebookCellExecutePrimary, description: localize('notebook.cell.executePrimary', "The contributed primary notebook cell execution button"), - proposed: true + proposed: 'notebookEditor' }, { key: 'interactive/toolbar', id: MenuId.InteractiveToolbar, description: localize('interactive.toolbar', "The contributed interactive toolbar menu"), - proposed: true + proposed: 'notebookEditor' }, { key: 'interactive/cell/title', id: MenuId.InteractiveCellTitle, description: localize('interactive.cell.title', "The contributed interactive cell title menu"), - proposed: true + proposed: 'notebookEditor' }, { key: 'testing/item/context', @@ -263,7 +249,7 @@ const apiMenus: IAPIMenu[] = [ id: MenuId.InlineCompletionsActions, description: localize('inlineCompletions.actions', "The actions shown when hovering on an inline completion"), supportsSubmenus: false, - proposed: true + proposed: 'inlineCompletions' }, ]; @@ -451,8 +437,7 @@ namespace schema { description: localize('vscode.extension.contributes.menus', "Contributes menu items to the editor"), type: 'object', properties: index(apiMenus, menu => menu.key, menu => ({ - description: menu.proposed ? `(${localize('proposed', "Proposed API")}) ${menu.description}` : menu.description, - deprecationMessage: menu.deprecationMessage, + markdownDescription: menu.proposed ? localize('proposed', "Proposed API, requires `enabledApiProposal: [\"{0}\"]` - {1}", menu.proposed, menu.description) : menu.description, type: 'array', items: menu.supportsSubmenus === false ? menuItem : { oneOf: [menuItem, submenuItem] } })), @@ -634,7 +619,7 @@ commandsExtensionPoint.setHandler(extensions => { title, source: extension.description.displayName ?? extension.description.name, shortTitle, - tooltip: isProposedApiEnabled(extension.description) ? title : undefined, + tooltip: title, category, precondition: ContextKeyExpr.deserialize(enablement), icon: absoluteIcon @@ -764,8 +749,8 @@ menusExtensionPoint.setHandler(extensions => { return; } - if (menu.proposed && !isProposedApiEnabled(extension.description)) { - collector.error(localize('proposedAPI.invalid', "{0} is a proposed menu identifier and is only available when running out of dev or with the following command line switch: --enable-proposed-api {1}", entry.key, extension.description.identifier.value)); + if (menu.proposed && !isProposedApiEnabled(extension.description, menu.proposed)) { + collector.error(localize('proposedAPI.invalid', "{0} is a proposed menu identifier. It requires 'package.json#enabledApiProposals: [\"{1}\"]' and is only available when running out of dev or with the following command line switch: --enable-proposed-api {2}", entry.key, menu.proposed, extension.description.identifier.value)); return; } diff --git a/src/vs/workbench/api/common/shared/treeDataTransfer.ts b/src/vs/workbench/api/common/shared/treeDataTransfer.ts index 123d05adeec..fb0f9acb536 100644 --- a/src/vs/workbench/api/common/shared/treeDataTransfer.ts +++ b/src/vs/workbench/api/common/shared/treeDataTransfer.ts @@ -16,11 +16,9 @@ export interface TreeDataTransferDTO { export namespace TreeDataTransferConverter { export function toITreeDataTransfer(value: TreeDataTransferDTO): ITreeDataTransfer { - const newDataTransfer: ITreeDataTransfer = { - items: new Map() - }; + const newDataTransfer: ITreeDataTransfer = new Map(); value.types.forEach((type, index) => { - newDataTransfer.items.set(type, { + newDataTransfer.set(type, { asString: async () => value.items[index].asString }); }); @@ -32,7 +30,7 @@ export namespace TreeDataTransferConverter { types: [], items: [] }; - const entries = Array.from(value.items.entries()); + const entries = Array.from(value.entries()); for (const entry of entries) { newDTO.types.push(entry[0]); newDTO.items.push({ diff --git a/src/vs/workbench/api/common/shared/workspaceContains.ts b/src/vs/workbench/api/common/shared/workspaceContains.ts index 74a283dce72..c9a1b852048 100644 --- a/src/vs/workbench/api/common/shared/workspaceContains.ts +++ b/src/vs/workbench/api/common/shared/workspaceContains.ts @@ -12,10 +12,12 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; import { ISearchService } from 'vs/workbench/services/search/common/search'; import { toWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { ILogService } from 'vs/platform/log/common/log'; const WORKSPACE_CONTAINS_TIMEOUT = 7000; export interface IExtensionActivationHost { + readonly logService: ILogService; readonly folders: readonly UriComponents[]; readonly forceUsingSearch: boolean; @@ -87,7 +89,7 @@ async function _activateIfGlobPatterns(host: IExtensionActivationHost, extension const timer = setTimeout(async () => { tokenSource.cancel(); - activate(`workspaceContainsTimeout:${globPatterns.join(',')}`); + host.logService.info(`Not activating extension '${extensionId.value}': Timed out while searching for 'workspaceContains' pattern ${globPatterns.join(',')}`); }, WORKSPACE_CONTAINS_TIMEOUT); let exists: boolean = false; diff --git a/src/vs/workbench/api/node/extHostCLIServer.ts b/src/vs/workbench/api/node/extHostCLIServer.ts index 6177f03c80f..2a03770b37e 100644 --- a/src/vs/workbench/api/node/extHostCLIServer.ts +++ b/src/vs/workbench/api/node/extHostCLIServer.ts @@ -22,6 +22,7 @@ export interface OpenCommandPipeArgs { gotoLineMode?: boolean; forceReuseWindow?: boolean; waitMarkerFilePath?: string; + remoteAuthority?: string | null; } export interface OpenExternalCommandPipeArgs { @@ -111,7 +112,7 @@ export class CLIServerBase { } private open(data: OpenCommandPipeArgs, res: http.ServerResponse) { - let { fileURIs, folderURIs, forceNewWindow, diffMode, addMode, forceReuseWindow, gotoLineMode, waitMarkerFilePath } = data; + const { fileURIs, folderURIs, forceNewWindow, diffMode, addMode, forceReuseWindow, gotoLineMode, waitMarkerFilePath, remoteAuthority } = data; const urisToOpen: IWindowOpenable[] = []; if (Array.isArray(folderURIs)) { for (const s of folderURIs) { @@ -135,12 +136,11 @@ export class CLIServerBase { } } } - if (urisToOpen.length) { - const waitMarkerFileURI = waitMarkerFilePath ? URI.file(waitMarkerFilePath) : undefined; - const preferNewWindow = !forceReuseWindow && !waitMarkerFileURI && !addMode; - const windowOpenArgs: IOpenWindowOptions = { forceNewWindow, diffMode, addMode, gotoLineMode, forceReuseWindow, preferNewWindow, waitMarkerFileURI }; - this._commands.executeCommand('_remoteCLI.windowOpen', urisToOpen, windowOpenArgs); - } + const waitMarkerFileURI = waitMarkerFilePath ? URI.file(waitMarkerFilePath) : undefined; + const preferNewWindow = !forceReuseWindow && !waitMarkerFileURI && !addMode; + const windowOpenArgs: IOpenWindowOptions = { forceNewWindow, diffMode, addMode, gotoLineMode, forceReuseWindow, preferNewWindow, waitMarkerFileURI, remoteAuthority }; + this._commands.executeCommand('_remoteCLI.windowOpen', urisToOpen, windowOpenArgs); + res.writeHead(200); res.end(); } diff --git a/src/vs/workbench/api/node/extHostLoggerService.ts b/src/vs/workbench/api/node/extHostLoggerService.ts index 67d9d52e055..d25dd875169 100644 --- a/src/vs/workbench/api/node/extHostLoggerService.ts +++ b/src/vs/workbench/api/node/extHostLoggerService.ts @@ -14,6 +14,7 @@ export class ExtHostLoggerService extends BaseExtHostLoggerService { protected override doCreateLogger(resource: URI, logLevel: LogLevel, options?: ILoggerOptions): ILogger { if (resource.scheme === Schemas.file) { + /* Create the logger in the Extension Host process to prevent loggers (log, output channels...) traffic over IPC */ return new SpdLogLogger(options?.name || generateUuid(), resource.fsPath, !options?.donotRotate, !!options?.donotUseFormatters, logLevel); } return super.doCreateLogger(resource, logLevel, options); diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 58ff7dc755b..91e523f7e66 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -26,7 +26,7 @@ import { IEditor } from 'vs/editor/common/editorCommon'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { SerializableGrid, ISerializableView, ISerializedGrid, Orientation, ISerializedNode, ISerializedLeafNode, Direction, IViewSize } from 'vs/base/browser/ui/grid/grid'; +import { SerializableGrid, ISerializableView, ISerializedGrid, Orientation, ISerializedNode, ISerializedLeafNode, Direction, IViewSize, Sizing } from 'vs/base/browser/ui/grid/grid'; import { Part } from 'vs/workbench/browser/part'; import { IStatusbarService } from 'vs/workbench/services/statusbar/browser/statusbar'; import { IFileService } from 'vs/platform/files/common/files'; @@ -43,7 +43,7 @@ import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { mark } from 'vs/base/common/performance'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; -import { Promises } from 'vs/base/common/async'; +import { DeferredPromise, Promises } from 'vs/base/common/async'; import { IBannerService } from 'vs/workbench/services/banner/browser/bannerService'; import { getVirtualWorkspaceScheme } from 'vs/platform/remote/common/remoteHosts'; import { Schemas } from 'vs/base/common/network'; @@ -51,6 +51,8 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b import { ActivitybarPart } from 'vs/workbench/browser/parts/activitybar/activitybarPart'; import { AuxiliaryBarPart, AUXILIARYBAR_ENABLED } from 'vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart'; +type PanelAlignment = 'left' | 'center' | 'right' | 'justified'; + export enum Settings { ACTIVITYBAR_VISIBLE = 'workbench.activityBar.visible', STATUSBAR_VISIBLE = 'workbench.statusBar.visible', @@ -58,6 +60,7 @@ export enum Settings { SIDEBAR_POSITION = 'workbench.sideBar.location', PANEL_POSITION = 'workbench.panel.defaultLocation', PANEL_OPENS_MAXIMIZED = 'workbench.panel.opensMaximized', + PANEL_ALIGNMENT = 'workbench.experimental.panel.alignment', ZEN_MODE_RESTORE = 'zenMode.restore', } @@ -217,7 +220,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi lastNonMaximizedWidth: 300, lastNonMaximizedHeight: 300, wasLastMaximized: false, - panelToRestore: undefined as string | undefined + panelToRestore: undefined as string | undefined, + alignment: 'center' as PanelAlignment }, auxiliaryBar: { @@ -396,6 +400,14 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Panel position this.updatePanelPosition(); + + // Panel alignment + const newPanelAlignmentValue = this.configurationService.getValue(Settings.PANEL_ALIGNMENT) ?? 'center'; + if (newPanelAlignmentValue !== this.state.panel.alignment) { + this.setPanelAlignment(newPanelAlignmentValue, skipLayout); + } + + if (!this.state.zenMode.active) { // Statusbar visibility @@ -451,17 +463,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.state.sideBar.width = this.workbenchGrid.getViewSize(this.sideBarPartView).width; } - if (position === Position.LEFT) { - this.workbenchGrid.moveViewTo(this.activityBarPartView, [2, 0]); - this.workbenchGrid.moveViewTo(this.sideBarPartView, [2, 1]); - this.workbenchGrid.moveViewTo(this.auxiliaryBarPartView, [2, 10]); - } else { - this.workbenchGrid.moveViewTo(this.auxiliaryBarPartView, [2, 0]); - this.workbenchGrid.moveViewTo(this.sideBarPartView, [2, 10]); - this.workbenchGrid.moveViewTo(this.activityBarPartView, [2, 10]); - } - - this.layout(); + // Move activity bar, side bar, and side panel + this.adjustPartPositions(position, this.state.panel.alignment); + // this.layout(); } private updateWindowBorder(skipLayout: boolean = false) { @@ -556,6 +560,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Panel position this.updatePanelPosition(); + this.state.panel.alignment = this.configurationService.getValue(Settings.PANEL_ALIGNMENT) ?? 'center'; + // Panel to restore if (!this.state.panel.hidden) { let panelToRestore = this.storageService.get(PanelPart.activePanelSettingsKey, StorageScope.WORKSPACE, this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Panel)?.id); @@ -701,11 +707,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return undefined; } - private whenReadyResolve: (() => void) | undefined; - protected readonly whenReady = new Promise(resolve => (this.whenReadyResolve = resolve)); + private readonly whenReadyPromise = new DeferredPromise(); + protected readonly whenReady = this.whenReadyPromise.p; - private whenRestoredResolve: (() => void) | undefined; - readonly whenRestored = new Promise(resolve => (this.whenRestoredResolve = resolve)); + private readonly whenRestoredPromise = new DeferredPromise(); + readonly whenRestored = this.whenRestoredPromise.p; private restored = false; isRestored(): boolean { @@ -905,11 +911,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Await for promises that we recorded to update // our ready and restored states properly. Promises.settled(layoutReadyPromises).finally(() => { - this.whenReadyResolve?.(); + this.whenReadyPromise.complete(); Promises.settled(layoutRestoredPromises).finally(() => { this.restored = true; - this.whenRestoredResolve?.(); + this.whenRestoredPromise.complete(); }); }); } @@ -1567,6 +1573,59 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return viewContainerModel.activeViewDescriptors.length >= 1; } + private adjustPartPositions(sideBarPosition: Position, panelAlignment: PanelAlignment): void { + // Move activity bar, side bar, and side panel + const sideBarNextToEditor = !(panelAlignment === 'center' || (sideBarPosition === Position.LEFT && panelAlignment === 'right') || (sideBarPosition === Position.RIGHT && panelAlignment === 'left')); + const auxiliaryBarNextToEditor = !(panelAlignment === 'center' || (sideBarPosition === Position.RIGHT && panelAlignment === 'right') || (sideBarPosition === Position.LEFT && panelAlignment === 'left')); + const preMoveSideBarSize = this.state.sideBar.hidden ? Sizing.Invisible(this.workbenchGrid.getViewCachedVisibleSize(this.sideBarPartView) ?? this.sideBarPartView.minimumWidth) : this.workbenchGrid.getViewSize(this.sideBarPartView).width; + const preMoveAuxiliaryBarSize = this.state.auxiliaryBar.hidden ? Sizing.Invisible(this.workbenchGrid.getViewCachedVisibleSize(this.auxiliaryBarPartView) ?? this.auxiliaryBarPartView.minimumWidth) : this.workbenchGrid.getViewSize(this.auxiliaryBarPartView).width; + + if (sideBarPosition === Position.LEFT) { + this.workbenchGrid.moveViewTo(this.activityBarPartView, [2, 0]); + this.workbenchGrid.moveView(this.sideBarPartView, preMoveSideBarSize, sideBarNextToEditor ? this.editorPartView : this.activityBarPartView, sideBarNextToEditor ? Direction.Left : Direction.Right); + if (auxiliaryBarNextToEditor) { + this.workbenchGrid.moveView(this.auxiliaryBarPartView, preMoveAuxiliaryBarSize, this.editorPartView, Direction.Right); + } else { + this.workbenchGrid.moveViewTo(this.auxiliaryBarPartView, [2, -1]); + } + } else { + this.workbenchGrid.moveViewTo(this.activityBarPartView, [2, -1]); + this.workbenchGrid.moveView(this.sideBarPartView, preMoveSideBarSize, sideBarNextToEditor ? this.editorPartView : this.activityBarPartView, sideBarNextToEditor ? Direction.Right : Direction.Left); + if (auxiliaryBarNextToEditor) { + this.workbenchGrid.moveView(this.auxiliaryBarPartView, preMoveAuxiliaryBarSize, this.editorPartView, Direction.Left); + } else { + this.workbenchGrid.moveViewTo(this.auxiliaryBarPartView, [2, 0]); + } + } + + // Moving views in the grid can cause them to re-distribute sizing unnecessarily + // Resize visible parts to the width they were before the operation + if (this.isVisible(Parts.SIDEBAR_PART)) { + this.workbenchGrid.resizeView(this.sideBarPartView, { + height: this.workbenchGrid.getViewSize(this.sideBarPartView).height, + width: preMoveSideBarSize as number + }); + } + + if (this.isVisible(Parts.AUXILIARYBAR_PART)) { + this.workbenchGrid.resizeView(this.auxiliaryBarPartView, { + height: this.workbenchGrid.getViewSize(this.auxiliaryBarPartView).height, + width: preMoveAuxiliaryBarSize as number + }); + } + } + + private setPanelAlignment(alignment: PanelAlignment, skipLayout?: boolean): void { + this.state.panel.alignment = alignment; + + // Panel alignment only applies to a panel in the bottom position + if (this.state.panel.position !== Position.BOTTOM) { + return; + } + + this.adjustPartPositions(this.state.sideBar.position, alignment); + } + private setPanelHidden(hidden: boolean, skipLayout?: boolean): void { const wasHidden = this.state.panel.hidden; this.state.panel.hidden = hidden; @@ -1909,15 +1968,107 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } - private arrangeEditorNodes(editorNode: ISerializedNode, panelNode: ISerializedNode, editorSectionWidth: number): ISerializedNode[] { - switch (this.state.panel.position) { - case Position.BOTTOM: - return [{ type: 'branch', data: [editorNode, panelNode], size: editorSectionWidth }]; - case Position.RIGHT: - return [editorNode, panelNode]; - case Position.LEFT: - return [panelNode, editorNode]; + private arrangeEditorNodes(nodes: { editor: ISerializedNode, sideBar?: ISerializedNode, auxiliaryBar?: ISerializedNode }, availableHeight: number, availableWidth: number): ISerializedNode { + if (!nodes.sideBar && !nodes.auxiliaryBar) { + nodes.editor.size = availableHeight; + return nodes.editor; } + + const result = [nodes.editor]; + nodes.editor.size = availableWidth; + if (nodes.sideBar) { + if (this.state.sideBar.position === Position.LEFT) { + result.splice(0, 0, nodes.sideBar); + } else { + result.push(nodes.sideBar); + } + + nodes.editor.size -= this.state.sideBar.hidden ? 0 : nodes.sideBar.size; + } + + if (nodes.auxiliaryBar) { + if (this.state.sideBar.position === Position.RIGHT) { + result.splice(0, 0, nodes.auxiliaryBar); + } else { + result.push(nodes.auxiliaryBar); + } + + nodes.editor.size -= this.state.auxiliaryBar.hidden ? 0 : nodes.auxiliaryBar.size; + } + + return { + type: 'branch', + data: result, + size: availableHeight + }; + } + + private arrangeMiddleSectionNodes(nodes: { editor: ISerializedNode, panel: ISerializedNode, activityBar: ISerializedNode, sideBar: ISerializedNode, auxiliaryBar: ISerializedNode }, availableWidth: number, availableHeight: number): ISerializedNode[] { + const activityBarSize = this.state.activityBar.hidden ? 0 : nodes.activityBar.size; + const sideBarSize = this.state.sideBar.hidden ? 0 : nodes.sideBar.size; + const auxiliaryBarSize = this.state.auxiliaryBar.hidden ? 0 : nodes.auxiliaryBar.size; + const panelSize = this.state.panel.hidden ? 0 : nodes.panel.size; + + const result = [] as ISerializedNode[]; + if (this.state.panel.position !== Position.BOTTOM) { + result.push(nodes.editor); + nodes.editor.size = availableWidth - activityBarSize - sideBarSize - panelSize - auxiliaryBarSize; + if (this.state.panel.position === Position.RIGHT) { + result.push(nodes.panel); + } else { + result.splice(0, 0, nodes.panel); + } + + if (this.state.sideBar.position === Position.LEFT) { + result.push(nodes.auxiliaryBar); + result.splice(0, 0, nodes.sideBar); + result.splice(0, 0, nodes.activityBar); + } else { + result.splice(0, 0, nodes.auxiliaryBar); + result.push(nodes.sideBar); + result.push(nodes.activityBar); + } + } else { + const panelAlignment = this.state.panel.alignment; + const sideBarPosition = this.state.sideBar.position; + const sideBarNextToEditor = !(panelAlignment === 'center' || (sideBarPosition === Position.LEFT && panelAlignment === 'right') || (sideBarPosition === Position.RIGHT && panelAlignment === 'left')); + const auxiliaryBarNextToEditor = !(panelAlignment === 'center' || (sideBarPosition === Position.RIGHT && panelAlignment === 'right') || (sideBarPosition === Position.LEFT && panelAlignment === 'left')); + + const editorSectionWidth = availableWidth - activityBarSize - (sideBarNextToEditor ? 0 : sideBarSize) - (auxiliaryBarNextToEditor ? 0 : auxiliaryBarSize); + result.push({ + type: 'branch', + data: [this.arrangeEditorNodes({ + editor: nodes.editor, + sideBar: sideBarNextToEditor ? nodes.sideBar : undefined, + auxiliaryBar: auxiliaryBarNextToEditor ? nodes.auxiliaryBar : undefined + }, availableHeight - panelSize, editorSectionWidth), nodes.panel], + size: editorSectionWidth + }); + + if (!sideBarNextToEditor) { + if (sideBarPosition === Position.LEFT) { + result.splice(0, 0, nodes.sideBar); + } else { + result.push(nodes.sideBar); + } + } + + if (!auxiliaryBarNextToEditor) { + if (sideBarPosition === Position.RIGHT) { + result.splice(0, 0, nodes.auxiliaryBar); + } else { + result.push(nodes.auxiliaryBar); + } + } + + if (sideBarPosition === Position.LEFT) { + result.splice(0, 0, nodes.activityBar); + } else { + result.push(nodes.activityBar); + } + } + + return result; } private createGridDescriptor(): ISerializedGrid { @@ -1935,7 +2086,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const statusBarHeight = this.statusBarPartView.minimumHeight; const activityBarWidth = this.activityBarPartView.minimumWidth; const middleSectionHeight = height - titleBarHeight - statusBarHeight; - const editorSectionWidth = width - (this.state.activityBar.hidden ? 0 : activityBarWidth) - (this.state.sideBar.hidden ? 0 : sideBarSize); const activityBarNode: ISerializedLeafNode = { type: 'leaf', @@ -1961,9 +2111,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const editorNode: ISerializedLeafNode = { type: 'leaf', data: { type: Parts.EDITOR_PART }, - size: this.state.panel.position === Position.BOTTOM ? - middleSectionHeight - (this.state.panel.hidden ? 0 : panelSize) : - editorSectionWidth - (this.state.panel.hidden ? 0 : panelSize), + size: 0, // Update based on sibling sizes visible: !this.state.editor.hidden }; @@ -1974,11 +2122,14 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi visible: !this.state.panel.hidden }; - const editorSectionNode = this.arrangeEditorNodes(editorNode, panelNode, editorSectionWidth); - const middleSection: ISerializedNode[] = this.state.sideBar.position === Position.LEFT - ? [activityBarNode, sideBarNode, ...editorSectionNode, auxiliaryBarNode] - : [auxiliaryBarNode, ...editorSectionNode, sideBarNode, activityBarNode]; + const middleSection: ISerializedNode[] = this.arrangeMiddleSectionNodes({ + activityBar: activityBarNode, + auxiliaryBar: auxiliaryBarNode, + editor: editorNode, + panel: panelNode, + sideBar: sideBarNode + }, width, middleSectionHeight); const result: ISerializedGrid = { root: { diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 22791e7c00f..1d41297143c 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -39,7 +39,8 @@ export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = { closeEmptyGroups: true, labelFormat: 'default', splitSizing: 'distribute', - splitOnDragAndDrop: true + splitOnDragAndDrop: true, + experimentalDisableClearInputOnSetInput: false //TODO@bpasero remove this setting in December }; export function impactsEditorPartOptions(event: IConfigurationChangeEvent): boolean { diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 411f50891eb..7a2aa70b8ac 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -30,7 +30,7 @@ import { combinedDisposable, dispose, MutableDisposable, toDisposable } from 'vs import { Severity, INotificationService } from 'vs/platform/notification/common/notification'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { Promises, RunOnceWorker } from 'vs/base/common/async'; +import { DeferredPromise, Promises, RunOnceWorker } from 'vs/base/common/async'; import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; import { IEditorGroupsAccessor, IEditorGroupView, fillActiveEditorViewState, EditorServiceImpl, IEditorGroupTitleHeight, IInternalEditorOpenOptions, IInternalMoveCopyOptions, IInternalEditorCloseOptions, IInternalEditorTitleControlOptions } from 'vs/workbench/browser/parts/editor/editor'; @@ -128,8 +128,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private readonly containerToolBarMenuDisposable = this._register(new MutableDisposable()); - private whenRestoredResolve: (() => void) | undefined; - readonly whenRestored = new Promise(resolve => (this.whenRestoredResolve = resolve)); + private readonly whenRestoredPromise = new DeferredPromise(); + readonly whenRestored = this.whenRestoredPromise.p; private isRestored = false; constructor( @@ -211,7 +211,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.element.appendChild(this.editorContainer); // Editor pane - this.editorPane = this._register(this.scopedInstantiationService.createInstance(EditorPanes, this.editorContainer, this)); + this.editorPane = this._register(this.scopedInstantiationService.createInstance(EditorPanes, this.editorContainer, this.accessor, this)); this._onDidChange.input = this.editorPane.onDidChangeSizeConstraints; // Track Focus @@ -232,7 +232,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Signal restored once editors have restored restoreEditorsPromise.finally(() => { this.isRestored = true; - this.whenRestoredResolve?.(); + this.whenRestoredPromise.complete(); }); // Register Listeners diff --git a/src/vs/workbench/browser/parts/editor/editorPane.ts b/src/vs/workbench/browser/parts/editor/editorPane.ts index 23e36f6c84e..f87a7651c19 100644 --- a/src/vs/workbench/browser/parts/editor/editorPane.ts +++ b/src/vs/workbench/browser/parts/editor/editorPane.ts @@ -122,7 +122,8 @@ export abstract class EditorPane extends Composite implements IEditorPane { * resources associated with the input should be freed. * * This method can be called based on different contexts, e.g. when opening - * a different editor control or when closing all editors in a group. + * a different input or different editor control or when closing all editors + * in a group. * * To monitor the lifecycle of editor inputs, you should not rely on this * method, rather refer to the listeners on `IEditorGroup` via `IEditorGroupService`. diff --git a/src/vs/workbench/browser/parts/editor/editorPanes.ts b/src/vs/workbench/browser/parts/editor/editorPanes.ts index 86562c72954..d12911518bb 100644 --- a/src/vs/workbench/browser/parts/editor/editorPanes.ts +++ b/src/vs/workbench/browser/parts/editor/editorPanes.ts @@ -13,7 +13,7 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorProgressService, LongRunningOperation } from 'vs/platform/progress/common/progress'; -import { IEditorGroupView, DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor'; +import { IEditorGroupView, DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS, IEditorGroupsAccessor } from 'vs/workbench/browser/parts/editor/editor'; import { Emitter } from 'vs/base/common/event'; import { assertIsDefined } from 'vs/base/common/types'; import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; @@ -84,6 +84,7 @@ export class EditorPanes extends Disposable { constructor( private parent: HTMLElement, + private accessor: IEditorGroupsAccessor, private groupView: IEditorGroupView, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -268,9 +269,18 @@ export class EditorPanes extends Disposable { // started will cancel the previous one. const operation = this.editorOperation.start(this.layoutService.isRestored() ? 800 : 3200); - // Set the input to the editor pane let cancelled = false; try { + + // Clear the current input before setting new input + // This ensures that a slow loading input will not + // be visible for the duration of the new input to + // load (https://github.com/microsoft/vscode/issues/34697) + if (this.accessor.partOptions.experimentalDisableClearInputOnSetInput !== true) { + editorPane.clearInput(); + } + + // Set the input to the editor pane await editorPane.setInput(editor, options, context, operation.token); if (!operation.isCurrent()) { @@ -309,7 +319,7 @@ export class EditorPanes extends Disposable { } closeEditor(editor: EditorInput): void { - if (this._activeEditorPane && this._activeEditorPane.input && editor.matches(this._activeEditorPane.input)) { + if (this._activeEditorPane?.input && editor.matches(this._activeEditorPane.input)) { this.doHideActiveEditorPane(); } } diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 06f3a883d2b..efcada70fec 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -30,7 +30,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { assertIsDefined } from 'vs/base/common/types'; import { IBoundarySashes } from 'vs/base/browser/ui/grid/gridview'; import { CompositeDragAndDropObserver } from 'vs/workbench/browser/dnd'; -import { Promises } from 'vs/base/common/async'; +import { DeferredPromise, Promises } from 'vs/base/common/async'; import { findGroup } from 'vs/workbench/services/editor/common/editorGroupFinder'; import { SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; @@ -222,11 +222,11 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro private _isReady = false; get isReady(): boolean { return this._isReady; } - private whenReadyResolve: (() => void) | undefined; - readonly whenReady = new Promise(resolve => (this.whenReadyResolve = resolve)); + private readonly whenReadyPromise = new DeferredPromise(); + readonly whenReady = this.whenReadyPromise.p; - private whenRestoredResolve: (() => void) | undefined; - readonly whenRestored = new Promise(resolve => (this.whenRestoredResolve = resolve)); + private readonly whenRestoredPromise = new DeferredPromise(); + readonly whenRestored = this.whenRestoredPromise.p; get hasRestorableState(): boolean { return !!this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY]; @@ -864,12 +864,12 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro this.setupDragAndDropSupport(parent, this.container); // Signal ready - this.whenReadyResolve?.(); + this.whenReadyPromise.complete(); this._isReady = true; // Signal restored Promises.settled(this.groups.map(group => group.whenRestored)).finally(() => { - this.whenRestoredResolve?.(); + this.whenRestoredPromise.complete(); }); return this.container; diff --git a/src/vs/workbench/browser/parts/editor/editorWithViewState.ts b/src/vs/workbench/browser/parts/editor/editorWithViewState.ts index 2f1abb31153..07a13f8955e 100644 --- a/src/vs/workbench/browser/parts/editor/editorWithViewState.ts +++ b/src/vs/workbench/browser/parts/editor/editorWithViewState.ts @@ -17,8 +17,6 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IExtUri } from 'vs/base/common/resources'; import { IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; -import { IEditorOptions } from 'vs/platform/editor/common/editor'; -import { CancellationToken } from 'vs/base/common/cancellation'; /** * Base class of editors that want to store and restore view state. @@ -65,14 +63,6 @@ export abstract class AbstractEditorWithViewState extends Edit } } - override async setInput(input: EditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { - - // Preserve current input view state before opening new - this.updateEditorViewState(this.input); - - await super.setInput(input, options, context, token); - } - override clearInput(): void { // Preserve current input view state before clearing diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index e84fe69e9a1..fc647e3e4bc 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -10,7 +10,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { MenuId, IMenuService, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { ITreeView, ITreeViewDescriptor, IViewsRegistry, Extensions, IViewDescriptorService, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeItemLabel, ViewContainer, ViewContainerLocation, ResolvableTreeItem, ITreeViewDragAndDropController, ITreeDataTransfer, TREE_ITEM_DATA_TRANSFER_TYPE } from 'vs/workbench/common/views'; +import { ITreeView, ITreeViewDescriptor, IViewsRegistry, Extensions, IViewDescriptorService, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeItemLabel, ViewContainer, ViewContainerLocation, ResolvableTreeItem, ITreeViewDragAndDropController, ITreeDataTransfer } from 'vs/workbench/common/views'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IThemeService, FileThemeIcon, FolderThemeIcon, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; @@ -57,6 +57,8 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance import { Command } from 'vs/editor/common/modes'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; +import { CodeDataTransfers, fillEditorsDragData } from 'vs/workbench/browser/dnd'; +import { Schemas } from 'vs/base/common/network'; export class TreeViewPane extends ViewPane { @@ -216,7 +218,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { this.collapseAllToggleContext = this.collapseAllToggleContextKey.bindTo(contextKeyService); this.refreshContextKey = new RawContextKey(`treeView.${this.id}.enableRefresh`, false, localize('treeView.enableRefresh', "Whether the tree view with id {0} enables refresh.", this.id)); this.refreshContext = this.refreshContextKey.bindTo(contextKeyService); - this.treeViewDnd = this.instantiationService.createInstance(CustomTreeViewDragAndDrop); + this.treeViewDnd = this.instantiationService.createInstance(CustomTreeViewDragAndDrop, this.id); this._register(this.themeService.onDidFileIconThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/)); this._register(this.themeService.onDidColorThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/)); @@ -1215,26 +1217,75 @@ export class TreeView extends AbstractTreeView { } } +interface TreeDragSourceInfo { + id: string, + itemHandles: string[]; +} + export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { - constructor(@ILabelService private readonly labelService: ILabelService) { } + private readonly treeMimeType: string; + constructor( + private readonly treeId: string, + @ILabelService private readonly labelService: ILabelService, + @IInstantiationService private readonly instantiationService: IInstantiationService) { + this.treeMimeType = `tree/${treeId.toLowerCase()}`; + } private dndController: ITreeViewDragAndDropController | undefined; set controller(controller: ITreeViewDragAndDropController | undefined) { this.dndController = controller; } + private addResourceInfoToTransfer(originalEvent: DragEvent, resources: URI[]) { + if (resources.length && originalEvent.dataTransfer) { + // Apply some datatransfer types to allow for dragging the element outside of the application + this.instantiationService.invokeFunction(accessor => fillEditorsDragData(accessor, resources, originalEvent)); + + // The only custom data transfer we set from the explorer is a file transfer + // to be able to DND between multiple code file explorers across windows + const fileResources = resources.filter(s => s.scheme === Schemas.file).map(r => r.fsPath); + if (fileResources.length) { + originalEvent.dataTransfer.setData(CodeDataTransfers.FILES, JSON.stringify(fileResources)); + } + } + } + onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void { if (originalEvent.dataTransfer) { - originalEvent.dataTransfer.setData(TREE_ITEM_DATA_TRANSFER_TYPE, - JSON.stringify((data as ElementsDragAndDropData).getData().map(treeItem => treeItem.handle))); + const treeItemsData = (data as ElementsDragAndDropData).getData(); + const resources: URI[] = []; + const sourceInfo: TreeDragSourceInfo = { + id: this.treeId, + itemHandles: [] + }; + treeItemsData.forEach(item => { + sourceInfo.itemHandles.push(item.handle); + if (item.resourceUri) { + resources.push(URI.revive(item.resourceUri)); + } + }); + this.addResourceInfoToTransfer(originalEvent, resources); + originalEvent.dataTransfer.setData(this.treeMimeType, + JSON.stringify(sourceInfo)); } } onDragOver(data: IDragAndDropData, targetElement: ITreeItem, targetIndex: number, originalEvent: DragEvent): boolean | ITreeDragOverReaction { - if (!this.dndController) { + const dndController = this.dndController; + if (!dndController || !originalEvent.dataTransfer || (dndController.supportedMimeTypes.length === 0)) { return false; } - return { accept: true, bubble: TreeDragOverBubble.Down, autoExpand: true }; + const dragContainersSupportedType = originalEvent.dataTransfer.types.some((value, index) => { + if (value === this.treeMimeType) { + return true; + } else { + return dndController.supportedMimeTypes.indexOf(value) >= 0; + } + }); + if (dragContainersSupportedType) { + return { accept: true, bubble: TreeDragOverBubble.Down, autoExpand: true }; + } + return false; } getDragURI(element: ITreeItem): string | null { @@ -1259,34 +1310,47 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { if (!originalEvent.dataTransfer || !this.dndController || !targetNode) { return; } - const treeDataTransfer: ITreeDataTransfer = { - items: new Map() - }; + const treeDataTransfer: ITreeDataTransfer = new Map(); let stringCount = Array.from(originalEvent.dataTransfer.items).reduce((previous, current) => { if (current.kind === 'string') { return previous + 1; } return previous; }, 0); + + let treeSourceInfo: TreeDragSourceInfo | undefined; await new Promise(resolve => { - if (!originalEvent.dataTransfer || !this.dndController || !targetNode) { + function decrementStringCount() { + stringCount--; + if (stringCount === 0) { + resolve(); + } + } + + const dndController = this.dndController; + if (!originalEvent.dataTransfer || !dndController || !targetNode) { return; } for (const dataItem of originalEvent.dataTransfer.items) { + const type = dataItem.type; if (dataItem.kind === 'string') { - const type = dataItem.type; - dataItem.getAsString(dataValue => { - treeDataTransfer.items.set(type, { - asString: () => Promise.resolve(dataValue) + if ((type === this.treeMimeType) || (dndController.supportedMimeTypes.indexOf(type) >= 0)) { + dataItem.getAsString(dataValue => { + if (type === this.treeMimeType) { + treeSourceInfo = JSON.parse(dataValue); + } else { + treeDataTransfer.set(type, { + asString: () => Promise.resolve(dataValue) + }); + } + decrementStringCount(); }); - stringCount--; - if (stringCount === 0) { - resolve(); - } - }); + } else { + decrementStringCount(); + } } } }); - return this.dndController.onDrop(treeDataTransfer, targetNode); + return this.dndController.onDrop(treeDataTransfer, targetNode, treeSourceInfo?.id, treeSourceInfo?.itemHandles); } } diff --git a/src/vs/workbench/browser/parts/views/viewsService.ts b/src/vs/workbench/browser/parts/views/viewsService.ts index e782029440e..4ecd0454eb0 100644 --- a/src/vs/workbench/browser/parts/views/viewsService.ts +++ b/src/vs/workbench/browser/parts/views/viewsService.ts @@ -271,6 +271,13 @@ export class ViewsService extends Disposable implements IViewsService { } else if (location === ViewContainerLocation.Panel) { this.paneCompositeService.hideActivePaneComposite(location); } + + // The blur event doesn't fire on WebKit when the focused element is hidden, + // so the context key needs to be forced here too otherwise a view may still + // think it's showing, breaking toggle commands. + if (this.focusedViewContextKey.get() === id) { + this.focusedViewContextKey.reset(); + } } else { view.setExpanded(false); } diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 74a8e47247f..cfc14325479 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -241,6 +241,11 @@ const registry = Registry.as(ConfigurationExtensions.Con 'default': false, 'description': localize('perEditorGroup', "Controls if the limit of maximum opened editors should apply per editor group or across all editor groups.") }, + 'workbench.editor.experimentalDisableClearInputOnSetInput': { + 'type': 'boolean', + 'default': false, + 'description': localize('experimentalDisableClearInputOnSetInput', "Experimental setting: do not change unless instructed.") + }, 'workbench.commandPalette.history': { 'type': 'number', 'description': localize('commandHistory', "Controls the number of recently used commands to keep in history for the command palette. Set to 0 to disable command history."), @@ -367,6 +372,13 @@ const registry = Registry.as(ConfigurationExtensions.Con 'description': localize('auxiliaryBarEnabled', "Controls whether the side panel opposite the side bar is enabled."), 'included': product.quality !== 'stable' }, + 'workbench.experimental.panel.alignment': { + 'type': 'string', + 'enum': ['left', 'center', 'right', 'justified'], + 'default': 'center', + 'description': localize('panelAlignment', "Controls the alignment of the panel (terminal, debug console, output, problems) and whether or not it spans beneath the side bar and side panel."), + 'included': product.quality !== 'stable' + }, } }); @@ -414,8 +426,7 @@ const registry = Registry.as(ConfigurationExtensions.Con }, 'window.titleSeparator': { 'type': 'string', - // allow-any-unicode-next-line - 'default': isMacintosh ? ' — ' : ' - ', + 'default': isMacintosh ? ' \u2014 ' : ' - ', 'markdownDescription': localize("window.titleSeparator", "Separator used by `window.title`.") }, 'window.menuBarVisibility': { diff --git a/src/vs/workbench/common/dialogs.ts b/src/vs/workbench/common/dialogs.ts index 5e0561583da..e6fd73a42b7 100644 --- a/src/vs/workbench/common/dialogs.ts +++ b/src/vs/workbench/common/dialogs.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { DeferredPromise } from 'vs/base/common/async'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IDialog, IDialogResult } from 'vs/platform/dialogs/common/dialogs'; @@ -34,11 +35,14 @@ export class DialogsModel extends Disposable implements IDialogsModel { readonly onDidShowDialog = this._onDidShowDialog.event; show(dialog: IDialog): IDialogHandle { - let resolver: (value?: IDialogResult) => void; + const promise = new DeferredPromise(); const item: IDialogViewItem = { args: dialog, - close: (result) => { this.dialogs.splice(0, 1); resolver(result); } + close: result => { + this.dialogs.splice(0, 1); + promise.complete(result); + } }; this.dialogs.push(item); @@ -46,7 +50,7 @@ export class DialogsModel extends Disposable implements IDialogsModel { return { item, - result: new Promise(resolve => { resolver = resolve; }) + result: promise.p }; } } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 682522d5688..b63bbd6a36d 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -864,7 +864,8 @@ interface IEditorPartConfiguration { decorations?: { badges?: boolean; colors?: boolean; - } + }, + experimentalDisableClearInputOnSetInput?: boolean; } export interface IEditorPartOptions extends IEditorPartConfiguration { diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index d837af0438b..df43db9d318 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -646,9 +646,7 @@ export interface ITreeDataTransferItem { asString(): Thenable; } -export interface ITreeDataTransfer { - items: Map; -} +export type ITreeDataTransfer = Map; export interface ITreeView extends IDisposable { @@ -838,9 +836,9 @@ export interface ITreeViewDataProvider { getChildren(element?: ITreeItem): Promise; } -export const TREE_ITEM_DATA_TRANSFER_TYPE = 'text/treeitems'; export interface ITreeViewDragAndDropController { - onDrop(elements: ITreeDataTransfer, target: ITreeItem): Promise; + readonly supportedMimeTypes: string[]; + onDrop(elements: ITreeDataTransfer, target: ITreeItem, sourceTreeId?: string, sourceTreeItemHandles?: string[]): Promise; } export interface IEditableData { diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index 310e7549105..b68d7d7036c 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -27,8 +27,7 @@ const $ = dom.$; export class StartDebugActionViewItem extends BaseActionViewItem { - // allow-any-unicode-next-line - private static readonly SEPARATOR = '─────────'; + private static readonly SEPARATOR = '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'; private container!: HTMLElement; private start!: HTMLElement; diff --git a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts index 6a05a0af677..2bc4947ea77 100644 --- a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts @@ -65,6 +65,7 @@ export class AdapterManager extends Disposable implements IAdapterManager { this._register(this.contextKeyService.onDidChangeContext(e => { if (e.affectsSome(this.debuggerWhenKeys)) { this.debuggersAvailable.set(this.hasEnabledDebuggers()); + this.updateDebugAdapterSchema(); } })); this.debugExtensionsAvailable = CONTEXT_DEBUG_EXTENSION_AVAILABLE.bindTo(contextKeyService); @@ -111,58 +112,7 @@ export class AdapterManager extends Disposable implements IAdapterManager { this.debuggers = this.debuggers.filter(d => removedTypes.indexOf(d.type) === -1); }); - // update the schema to include all attributes, snippets and types from extensions. - const items = (launchSchema.properties!['configurations'].items); - const taskSchema = TaskDefinitionRegistry.getJsonSchema(); - const definitions: IJSONSchemaMap = { - 'common': { - properties: { - 'name': { - type: 'string', - description: nls.localize('debugName', "Name of configuration; appears in the launch configuration dropdown menu."), - default: 'Launch' - }, - 'debugServer': { - type: 'number', - description: nls.localize('debugServer', "For debug extension development only: if a port is specified VS Code tries to connect to a debug adapter running in server mode"), - default: 4711 - }, - 'preLaunchTask': { - anyOf: [taskSchema, { - type: ['string'] - }], - default: '', - defaultSnippets: [{ body: { task: '', type: '' } }], - description: nls.localize('debugPrelaunchTask', "Task to run before debug session starts.") - }, - 'postDebugTask': { - anyOf: [taskSchema, { - type: ['string'], - }], - default: '', - defaultSnippets: [{ body: { task: '', type: '' } }], - description: nls.localize('debugPostDebugTask', "Task to run after debug session ends.") - }, - 'presentation': presentationSchema, - 'internalConsoleOptions': INTERNAL_CONSOLE_OPTIONS_SCHEMA, - } - } - }; - launchSchema.definitions = definitions; - items.oneOf = []; - items.defaultSnippets = []; - this.debuggers.forEach(adapter => { - const schemaAttributes = adapter.getSchemaAttributes(definitions); - if (schemaAttributes && items.oneOf) { - items.oneOf.push(...schemaAttributes); - } - const configurationSnippets = adapter.configurationSnippets; - if (configurationSnippets && items.defaultSnippets) { - items.defaultSnippets.push(...configurationSnippets); - } - }); - jsonRegistry.registerSchema(launchSchemaId, launchSchema); - + this.updateDebugAdapterSchema(); this._onDidDebuggersExtPointRead.fire(); }); @@ -176,6 +126,60 @@ export class AdapterManager extends Disposable implements IAdapterManager { }); } + private updateDebugAdapterSchema(): void { + // update the schema to include all attributes, snippets and types from extensions. + const items = (launchSchema.properties!['configurations'].items); + const taskSchema = TaskDefinitionRegistry.getJsonSchema(); + const definitions: IJSONSchemaMap = { + 'common': { + properties: { + 'name': { + type: 'string', + description: nls.localize('debugName', "Name of configuration; appears in the launch configuration dropdown menu."), + default: 'Launch' + }, + 'debugServer': { + type: 'number', + description: nls.localize('debugServer', "For debug extension development only: if a port is specified VS Code tries to connect to a debug adapter running in server mode"), + default: 4711 + }, + 'preLaunchTask': { + anyOf: [taskSchema, { + type: ['string'] + }], + default: '', + defaultSnippets: [{ body: { task: '', type: '' } }], + description: nls.localize('debugPrelaunchTask', "Task to run before debug session starts.") + }, + 'postDebugTask': { + anyOf: [taskSchema, { + type: ['string'], + }], + default: '', + defaultSnippets: [{ body: { task: '', type: '' } }], + description: nls.localize('debugPostDebugTask', "Task to run after debug session ends.") + }, + 'presentation': presentationSchema, + 'internalConsoleOptions': INTERNAL_CONSOLE_OPTIONS_SCHEMA, + } + } + }; + launchSchema.definitions = definitions; + items.oneOf = []; + items.defaultSnippets = []; + this.debuggers.forEach(adapter => { + const schemaAttributes = adapter.getSchemaAttributes(definitions); + if (schemaAttributes && items.oneOf) { + items.oneOf.push(...schemaAttributes); + } + const configurationSnippets = adapter.configurationSnippets; + if (configurationSnippets && items.defaultSnippets) { + items.defaultSnippets.push(...configurationSnippets); + } + }); + jsonRegistry.registerSchema(launchSchemaId, launchSchema); + } + registerDebugAdapterFactory(debugTypes: string[], debugAdapterLauncher: IDebugAdapterFactory): IDisposable { debugTypes.forEach(debugType => this.debugAdapterFactories.set(debugType, debugAdapterLauncher)); this.debuggersAvailable.set(this.hasEnabledDebuggers()); diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index aa6e8b3b889..429f9bde87f 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -916,13 +916,19 @@ export class DebugSession implements IDebugSession { this.passFocusScheduler.cancel(); this.stoppedDetails.push(event.body); await this.fetchThreads(event.body); + // If the focus for the current session is on a non-existent thread, clear the focus. + const focusedThread = this.debugService.getViewModel().focusedThread; + const focusedThreadDoesNotExist = focusedThread !== undefined && focusedThread.session === this && !this.threads.has(focusedThread.threadId); + if (focusedThreadDoesNotExist) { + this.debugService.focusStackFrame(undefined, undefined); + } const thread = typeof event.body.threadId === 'number' ? this.getThread(event.body.threadId) : undefined; if (thread) { // Call fetch call stack twice, the first only return the top stack frame. // Second retrieves the rest of the call stack. For performance reasons #25605 const promises = this.model.fetchCallStack(thread); const focus = async () => { - if (!event.body.preserveFocusHint && thread.getCallStack().length) { + if (focusedThreadDoesNotExist || (!event.body.preserveFocusHint && thread.getCallStack().length)) { const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame; if (!focusedStackFrame || focusedStackFrame.thread.session === this) { // Only take focus if nothing is focused, or if the focus is already on the current session diff --git a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css index 400f056ad1c..64f1d065ccd 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -31,6 +31,12 @@ margin-top: -1px; } +.codicon-debug-breakpoint.codicon-debug-stackframe-focused::after, +.codicon-debug-breakpoint.codicon-debug-stackframe::after { + content: '\eb8a'; + position: absolute; +} + .monaco-editor .debug-top-stack-frame-column { font: normal normal normal 16px/1 codicon; text-rendering: auto; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index e3712271e12..b2d08fc2d68 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -1550,10 +1550,8 @@ export class ExtensionEditor extends EditorPane { $('td', undefined, l.id), $('td', undefined, l.name), $('td', undefined, ...join(l.extensions.map(ext => $('code', undefined, ext)), ' ')), - // allow-any-unicode-next-line - $('td', undefined, document.createTextNode(l.hasGrammar ? '✔︎' : '—')), - // allow-any-unicode-next-line - $('td', undefined, document.createTextNode(l.hasSnippets ? '✔︎' : '—')) + $('td', undefined, document.createTextNode(l.hasGrammar ? '✔︎' : '\u2014')), + $('td', undefined, document.createTextNode(l.hasSnippets ? '✔︎' : '\u2014')) )) ) ); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 23ed1179884..c376c5333c1 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -702,24 +702,22 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension queryGallery(token: CancellationToken): Promise>; queryGallery(options: IQueryOptions, token: CancellationToken): Promise>; - queryGallery(arg1: any, arg2?: any): Promise> { + async queryGallery(arg1: any, arg2?: any): Promise> { const options: IQueryOptions = CancellationToken.isCancellationToken(arg1) ? {} : arg1; const token: CancellationToken = CancellationToken.isCancellationToken(arg1) ? arg1 : arg2; options.text = options.text ? this.resolveQueryText(options.text) : options.text; - return this.extensionManagementService.getExtensionsReport() - .then(report => { - const maliciousSet = getMaliciousExtensionsSet(report); - - return this.galleryService.query(options, token) - .then(result => mapPager(result, gallery => this.fromGallery(gallery, maliciousSet))) - .then(undefined, err => { - if (/No extension gallery service configured/.test(err.message)) { - return Promise.resolve(singlePagePager([])); - } - - return Promise.reject>(err); - }); - }); + + const report = await this.extensionManagementService.getExtensionsReport(); + const maliciousSet = getMaliciousExtensionsSet(report); + try { + const result = await this.galleryService.query(options, token); + return mapPager(result, gallery => this.fromGallery(gallery, maliciousSet)); + } catch (error) { + if (/No extension gallery service configured/.test(error.message)) { + return Promise.resolve(singlePagePager([])); + } + throw error; + } } private resolveQueryText(text: string): string { diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/extensions.contribution.ts index 61ded27f026..55cb5746c3c 100644 --- a/src/vs/workbench/contrib/extensions/electron-sandbox/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/electron-sandbox/extensions.contribution.ts @@ -23,6 +23,7 @@ import { IExtensionRecommendationNotificationService } from 'vs/platform/extensi import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { ExtensionRecommendationNotificationServiceChannel } from 'vs/platform/extensionRecommendations/electron-sandbox/extensionRecommendationsIpc'; import { Codicon } from 'vs/base/common/codicons'; +import { RemoteExtensionsInitializerContribution } from 'vs/workbench/contrib/extensions/electron-sandbox/remoteExtensionsInit'; // Running Extensions Editor Registry.as(EditorExtensions.EditorPane).registerEditorPane( @@ -60,6 +61,7 @@ class ExtensionsContributions implements IWorkbenchContribution { const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(ExtensionsContributions, LifecyclePhase.Starting); +workbenchRegistry.registerWorkbenchContribution(RemoteExtensionsInitializerContribution, LifecyclePhase.Restored); // Register Commands diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/remoteExtensionsInit.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/remoteExtensionsInit.ts new file mode 100644 index 00000000000..d1d29adc444 --- /dev/null +++ b/src/vs/workbench/contrib/extensions/electron-sandbox/remoteExtensionsInit.ts @@ -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. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { IStorageService, IS_NEW_KEY, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { AbstractExtensionsInitializer } from 'vs/platform/userDataSync/common/extensionsSync'; +import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; +import { IRemoteUserData, IUserDataSyncStoreManagementService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; +import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; + +export class RemoteExtensionsInitializerContribution implements IWorkbenchContribution { + constructor( + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, + @IStorageService private readonly storageService: IStorageService, + @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, + @IUserDataSyncStoreManagementService private readonly userDataSyncStoreManagementService: IUserDataSyncStoreManagementService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @ILogService private readonly logService: ILogService, + @IAuthenticationService private readonly authenticationService: IAuthenticationService, + @IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService, + ) { + this.initializeRemoteExtensions(); + } + + private async initializeRemoteExtensions(): Promise { + const connection = this.remoteAgentService.getConnection(); + const localExtensionManagementServer = this.extensionManagementServerService.localExtensionManagementServer; + const remoteExtensionManagementServer = this.extensionManagementServerService.remoteExtensionManagementServer; + // Skip: Not a remote window + if (!connection || !remoteExtensionManagementServer) { + return; + } + // Skip: Not a native window + if (!localExtensionManagementServer) { + return; + } + // Skip: No UserdataSyncStore is configured + if (!this.userDataSyncStoreManagementService.userDataSyncStore) { + return; + } + const newRemoteConnectionKey = `${IS_NEW_KEY}.${connection.remoteAuthority}`; + // Skip: Not a new remote connection + if (!this.storageService.getBoolean(newRemoteConnectionKey, StorageScope.GLOBAL, true)) { + this.logService.trace(`Skipping initializing remote extensions because the window with this remote authority was opened before.`); + return; + } + this.storageService.store(newRemoteConnectionKey, false, StorageScope.GLOBAL, StorageTarget.MACHINE); + // Skip: Not a new workspace + if (!this.storageService.isNew(StorageScope.WORKSPACE)) { + this.logService.trace(`Skipping initializing remote extensions because this workspace was opened before.`); + return; + } + // Skip: No account is provided to initialize + const resolvedAuthority = await this.remoteAuthorityResolverService.resolveAuthority(connection.remoteAuthority); + if (!resolvedAuthority.options?.authenticationSession) { + return; + } + + const sessions = await this.authenticationService.getSessions(resolvedAuthority.options?.authenticationSession.providerId); + const session = sessions.find(s => s.id === resolvedAuthority.options?.authenticationSession?.id); + // Skip: Session is not found + if (!session) { + this.logService.info('Skipping initializing remote extensions because the account with given session id is not found', resolvedAuthority.options.authenticationSession.id); + return; + } + + const userDataSyncStoreClient = this.instantiationService.createInstance(UserDataSyncStoreClient, this.userDataSyncStoreManagementService.userDataSyncStore.url); + userDataSyncStoreClient.setAuthToken(session.accessToken, resolvedAuthority.options.authenticationSession.providerId); + const userData = await userDataSyncStoreClient.read(SyncResource.Extensions, null); + + const serviceCollection = new ServiceCollection(); + serviceCollection.set(IExtensionManagementService, remoteExtensionManagementServer.extensionManagementService); + const instantiationService = this.instantiationService.createChild(serviceCollection); + const extensionsToInstallInitializer = instantiationService.createInstance(RemoteExtensionsInitializer); + + await extensionsToInstallInitializer.initialize(userData); + } +} + +class RemoteExtensionsInitializer extends AbstractExtensionsInitializer { + + constructor( + @IExtensionManagementService extensionManagementService: IExtensionManagementService, + @IIgnoredExtensionsManagementService ignoredExtensionsManagementService: IIgnoredExtensionsManagementService, + @IFileService fileService: IFileService, + @IEnvironmentService environmentService: IEnvironmentService, + @ILogService logService: ILogService, + @IUriIdentityService uriIdentityService: IUriIdentityService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, + @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, + ) { + super(extensionManagementService, ignoredExtensionsManagementService, fileService, environmentService, logService, uriIdentityService); + } + + protected override async doInitialize(remoteUserData: IRemoteUserData): Promise { + const remoteExtensions = await this.parseExtensions(remoteUserData); + if (!remoteExtensions) { + this.logService.info('No synced extensions exist while initializing remote extensions.'); + return; + } + const installedExtensions = await this.extensionManagementService.getInstalled(); + const { newExtensions } = this.generatePreview(remoteExtensions, installedExtensions); + if (!newExtensions.length) { + this.logService.trace('No new remote extensions to install.'); + return; + } + const extensionsToInstall = await this.extensionGalleryService.getExtensions(newExtensions, CancellationToken.None); + if (extensionsToInstall.length) { + await Promise.allSettled(extensionsToInstall.map(async e => { + const manifest = await this.extensionGalleryService.getManifest(e, CancellationToken.None); + if (manifest && this.extensionManifestPropertiesService.canExecuteOnWorkspace(manifest)) { + await this.extensionManagementService.installFromGallery(e); + } + })); + } + } +} diff --git a/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts b/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts index 90f7bba43b4..68fc90c3a16 100644 --- a/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts +++ b/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts @@ -90,7 +90,7 @@ export class WorkspaceWatcher extends Disposable { else if (msg.indexOf('EUNKNOWN') >= 0) { this.notificationService.prompt( Severity.Warning, - localize('eshutdownError', "File changes watcher stopped unexpectedly. Please reload the window to enable the watcher again."), + localize('eshutdownError', "File changes watcher stopped unexpectedly. A reload of the window may enable the watcher again unless the workspace cannot be watched for file changes."), [{ label: localize('reload', "Reload"), run: () => this.hostService.reload() diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index 3117b72ae95..e5ec850f34f 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -322,7 +322,7 @@ export class InteractiveEditor extends EditorPane { this.#notebookWidget.value!.setOptions({ isReadOnly: true }); - this.#widgetDisposableStore.add(this.#notebookWidget.value!.onDidFocus(() => this.#onDidFocusWidget.fire())); + this.#widgetDisposableStore.add(this.#notebookWidget.value!.onDidFocusWidget(() => this.#onDidFocusWidget.fire())); this.#widgetDisposableStore.add(model.notebook.onDidChangeContent(() => { (model as ComplexNotebookEditorModel).setDirty(false); })); diff --git a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts index c91e9c0a29d..92c5e9c0348 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts +++ b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts @@ -253,7 +253,7 @@ ExtensionsRegistry.registerExtensionPoint({ id: { type: 'string', description: localize('vscode.extension.contributes.localizations.translations.id', "Id of VS Code or Extension for which this translation is contributed to. Id of VS Code is always `vscode` and of extension should be in format `publisherId.extensionName`."), - pattern: '^((vscode)|([a-z0-9A-Z][a-z0-9\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\-A-Z]*))$', + pattern: '^((vscode)|([a-z0-9A-Z][a-z0-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-Z-]*))$', patternErrorMessage: localize('vscode.extension.contributes.localizations.translations.id.pattern', "Id should be `vscode` or in format `publisherId.extensionName` for translating VS code or an extension respectively.") }, path: { diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index 469c0cf0e64..073988a4dcf 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -104,7 +104,7 @@ Registry.as(Extensions.Configuration).registerConfigurat 'problems.compareOrder': { 'description': Messages.PROBLEMS_PANEL_CONFIGURATION_COMPARE_ORDER, 'type': 'string', - 'default': ['severity'], + 'default': 'severity', 'enum': ['severity', 'position'], 'enumDescriptions': [ Messages.PROBLEMS_PANEL_CONFIGURATION_COMPARE_ORDER_SEVERITY, diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts index 77554cd2187..1c782a7dcb3 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts @@ -347,7 +347,7 @@ export class KernelStatus extends Disposable implements IWorkbenchContribution { this._kernelInfoElement.clear(); let { selected, suggestions, all } = this._notebookKernelService.getMatchingKernel(notebook); - const suggested = suggestions[0]; + const suggested = (suggestions.length === 1 && all.length === 1) ? suggestions[0] : undefined; let isSuggested = false; if (all.length === 0) { diff --git a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts index c55e7d9928c..cc3c11bfc70 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { maxIndex, minIndex } from 'vs/base/common/arrays'; import { Iterable } from 'vs/base/common/iterator'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { URI, UriComponents } from 'vs/base/common/uri'; @@ -17,7 +16,7 @@ import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cel import { cellExecutionArgs, CellToolbarOrder, CELL_TITLE_CELL_GROUP_ID, executeNotebookCondition, getContextFromActiveEditor, getContextFromUri, INotebookActionContext, INotebookCellActionContext, INotebookCellToolbarActionContext, INotebookCommandContext, NotebookAction, NotebookCellAction, NotebookMultiCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, parseMultiCellExecutionArgs } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID, NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_MISSING_KERNEL_EXTENSION } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; -import { CellKind, NotebookSetting, NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, NotebookCellExecutionState, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -231,7 +230,7 @@ registerAction2(class ExecuteAboveCells extends NotebookMultiCellAction { if (context.ui) { endCellIdx = context.notebookEditor.getCellIndex(context.cell); } else { - endCellIdx = maxIndex(context.selectedCells, cell => context.notebookEditor.getCellIndex(cell)); + endCellIdx = Math.min(...context.selectedCells.map(cell => context.notebookEditor.getCellIndex(cell))); } if (typeof endCellIdx === 'number') { @@ -277,7 +276,7 @@ registerAction2(class ExecuteCellAndBelow extends NotebookMultiCellAction { if (context.ui) { startCellIdx = context.notebookEditor.getCellIndex(context.cell); } else { - startCellIdx = minIndex(context.selectedCells, cell => context.notebookEditor.getCellIndex(cell)); + startCellIdx = Math.min(...context.selectedCells.map(cell => context.notebookEditor.getCellIndex(cell))); } if (typeof startCellIdx === 'number') { diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts index 14c6dc4b5dd..17955e7c261 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts @@ -23,7 +23,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IAction } from 'vs/base/common/actions'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellActionView'; +import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView'; import { collapsedIcon, expandedIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { OutputContainer } from 'vs/workbench/contrib/notebook/browser/diff/diffElementOutputs'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffElementOutputs.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffElementOutputs.ts index e6d0c482d5d..437372d6e8b 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffElementOutputs.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffElementOutputs.ts @@ -10,7 +10,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { DiffElementViewModelBase, SideBySideDiffElementViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; import { DiffSide, INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; import { ICellOutputViewModel, IRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets'; +import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellWidgets'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { BUILTIN_RENDERER_ID, NotebookCellOutputsSplice } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts index de1e6d09378..eef7e7fff9a 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts @@ -408,11 +408,14 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD const diffResult = await this.notebookEditorWorkerService.computeDiff(this._model.original.resource, this._model.modified.resource); NotebookTextDiffEditor.prettyChanges(this._model, diffResult.cellsDiff); const { viewModels, firstChangeIndex } = NotebookTextDiffEditor.computeDiff(this.instantiationService, this.configurationService, this._model, this._eventDispatcher!, diffResult); + const isSame = this._isViewModelTheSame(viewModels); - this._originalWebview?.removeInsets([...this._originalWebview?.insetMapping.keys()]); - this._modifiedWebview?.removeInsets([...this._modifiedWebview?.insetMapping.keys()]); + if (!isSame) { + this._originalWebview?.removeInsets([...this._originalWebview?.insetMapping.keys()]); + this._modifiedWebview?.removeInsets([...this._modifiedWebview?.insetMapping.keys()]); + this._setViewModel(viewModels); + } - this._setViewModel(viewModels); // this._diffElementViewModels = viewModels; // this._list.splice(0, this._list.length, this._diffElementViewModels); @@ -423,7 +426,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } } - private _setViewModel(viewModels: DiffElementViewModelBase[]) { + private _isViewModelTheSame(viewModels: DiffElementViewModelBase[]) { let isSame = true; if (this._diffElementViewModels.length === viewModels.length) { for (let i = 0; i < viewModels.length; i++) { @@ -440,13 +443,12 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD isSame = false; } - if (isSame) { - return; - } + return isSame; + } + private _setViewModel(viewModels: DiffElementViewModelBase[]) { this._diffElementViewModels = viewModels; this._list.splice(0, this._list.length, this._diffElementViewModels); - } /** diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts index 914d2b81480..6b3b5f56da3 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts @@ -24,7 +24,7 @@ import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellActionView'; +import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 399f893bed9..5585fd0cb4c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -30,7 +30,7 @@ import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEd import { isCompositeNotebookEditorInput, NotebookEditorInput, NotebookEditorInputOptions } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { NotebookService } from 'vs/workbench/contrib/notebook/browser/notebookServiceImpl'; -import { CellKind, CellUri, IResolvedNotebookEditorModel, NotebookDocumentBackupData, NotebookWorkingCopyTypeIdentifier, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, CellUri, IResolvedNotebookEditorModel, NotebookDocumentBackupData, NotebookWorkingCopyTypeIdentifier, NotebookSetting, ICellOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; @@ -372,13 +372,32 @@ class CellInfoContentProvider { return result; } + private parseStreamOutput(resource: URI, op?: ICellOutput) { + if (!op) { + return; + } + + const streamOutputData = getStreamOutputData(op.outputs); + if (streamOutputData) { + const result = this._modelService.createModel( + streamOutputData, + this._modeService.create('plaintext'), + resource + ); + + return result; + } + + return; + } + async provideOutputTextContent(resource: URI): Promise { const existing = this._modelService.getModel(resource); if (existing) { return existing; } - const data = CellUri.parseCellUri(resource, Schemas.vscodeNotebookCellOutput); + const data = CellUri.parseCellOutputUri(resource); if (!data) { return null; } @@ -389,36 +408,33 @@ class CellInfoContentProvider { const mode = this._modeService.create('json'); for (const cell of ref.object.notebook.cells) { - if (cell.handle === data.handle) { - if (cell.outputs.length === 1) { - // single output - const streamOutputData = getStreamOutputData(cell.outputs[0].outputs); - if (streamOutputData) { - result = this._modelService.createModel( - streamOutputData, - this._modeService.create('plaintext'), - resource - ); - break; - } - } + if (cell.handle !== data.handle) { + continue; + } - const content = JSON.stringify(cell.outputs.map(output => ({ - metadata: output.metadata, - outputItems: output.outputs.map(opit => ({ - mimeType: opit.mime, - data: opit.data.toString() - })) - }))); - const edits = format(content, undefined, {}); - const outputSource = applyEdits(content, edits); - result = this._modelService.createModel( - outputSource, - mode, - resource - ); + const op = cell.outputs.find(op => op.outputId === data.outputId); + const streamOutputData = this.parseStreamOutput(resource, op); + if (streamOutputData) { + result = streamOutputData; break; } + + const content = JSON.stringify(cell.outputs.map(output => ({ + metadata: output.metadata, + outputItems: output.outputs.map(opit => ({ + mimeType: opit.mime, + data: opit.data.toString() + })) + }))); + + const edits = format(content, undefined, {}); + const outputSource = applyEdits(content, edits); + result = this._modelService.createModel( + outputSource, + mode, + resource + ); + break; } if (result) { @@ -754,8 +770,9 @@ configurationRegistry.registerConfiguration({ }, [NotebookSetting.globalToolbarShowLabel]: { description: nls.localize('notebook.globalToolbarShowLabel', "Control whether the actions on the notebook toolbar should render label or not."), - type: 'boolean', - default: true, + type: 'string', + enum: ['always', 'never', 'dynamic'], + default: 'always', tags: ['notebookLayout'] }, [NotebookSetting.textOutputLineLimit]: { diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.layout.md b/src/vs/workbench/contrib/notebook/browser/notebook.layout.md index d12d4cc249b..715ff1317b1 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.layout.md +++ b/src/vs/workbench/contrib/notebook/browser/notebook.layout.md @@ -1,6 +1,74 @@ -# Notebook Layout +The notebook editor is a virtualized list view rendered in two contexts (mainframe and webview/iframe). It's on top of the builtin list/tree view renderer but its experience is different from traditional list views like File Explorer and Settings Editor. This doc covers the architecture of the notebook editor and layout optimiziations we experimented. -The notebook editor is a virtualized list view rendered in two contexts (mainframe and webview/iframe). Since most elements' positions are absoulte and there is latency between the two frames, we have multiple optimizations to ensure smooth (we try our best) perceived user experience. The optimizations are mostly around: +# Archtecture + +## Notebook model resolution + +![arch](https://user-images.githubusercontent.com/876920/141845889-abe0384e-0093-4b08-831a-04424a4b8101.png) + +## Viewport rendering + +The rendering of notebook list view is a "guess and validate" process. It will calcuate how many cells/rows it can render within the viewport, have them all rendered, and then ask for their real dimension, and based on the cell/row dimensions it will decide if it needs to render more cells (if there are still some room in the viewport) or remove a few. + +For short, the process is more or less + +* Render cell/row (DOM write) +* Read dimensions (DOM read) + +The catch here is while rendering a cell/row, if we happen to perform any DOM read operation between DOM write, it will trigger forced reflow and block the UI. To prevent this we would batch all DOM read operatiosn and postpone them untill the list view requests them. + +The worflow of rendering a code cell with a text output is like below and all operations are synchronous + +![render in the core](https://user-images.githubusercontent.com/876920/142806570-a477d315-40f3-4e0c-8079-f2867d5f3e88.png) + +When the notebook document contains markdown cells or rich outputs, the workflow is a bit more complex and become asynchornously partially due to the fact the markdown and rich outputs are rendered in a separate webview/iframe. While the list view renders the cell/row, it will send requests to the webview for output rendering, the rendering result (like dimensions of the output elements) won't come back in current frame. Once we receive the output rendering results from the webview (say next frame), we would ask the list view to adjust the position/dimension of the cell and ones below. + +![render outputs in the webview/iframe](https://user-images.githubusercontent.com/876920/142276957-f73a155e-70cb-4066-b5cc-5f451c1c91c8.png) + + +## Cell rendering + +The rendering of cells in the notebook editor consists of following steps: + +* Update reused DOM nodes in the template and cell decorations +* Set up context for the cell and toolbars +* Update cell toolbar, run toolbar and insertion toolbar between cells +* Render cell +* Register listeners for: + * Notebook layout change + * Cell layout change + * Cell state change: Folding, Collapse, Focus + +## Focus Tracking + +Due to the nature of virtualization (list view) and two layers architecture, the focus tracking is more complex compared to file explorer or monaco editor. When a notebook is *focused*, the `document.activeElement` can be + +* Monaco editor, when users focus on a cell editor + * `textarea` when users focus the text + * Widgets +* Webview/iframe, when users focus on markdown cell or rich outputs rendered rendered in iframe +* List view container, when users focus on cell container +* Focusable element inside the notebook editor + * Builtin output (e.g., text output) + * Find Widget + * Cell statusbar + * Toolbars + +The catch here is if the focus is on a monaco editor, instead of the list view container, when the cell is moved out of view, the list view removes the cell row from the DOM tree. The `document.activeElement` will fall back `document.body` when that happens. To ensure that the notebook editor doesn't blur, we need to move focus back to list view container when the focused cell is moved out of view. More importantly, focus the cell editor again when the cell is visible again (if the cell is still the *active* cell). + +Copy in Notebook depends on the focus tracking + +* Send `document.executeCommand('copy')` if users select text in output rendered in main frame by builtin renderer +* Request webview copy if the focus is inside the webview +* Copy cells if the focus is on notebook cell list +* Copy text if the focus is in cell editor (monaco editor) + +![diagram](https://user-images.githubusercontent.com/876920/141730905-2818043e-1a84-45d3-ad27-83bd89235ca5.png) + + +# Optimizations + +Since most elements' positions are absoulte and there is latency between the two frames, we have multiple optimizations to ensure smooth (we try our best) perceived user experience. The optimizations are mostly around: * Ensure the elements in curent viewport are stable when other elements dimensions update * Fewer layout messages between the main and iframe @@ -66,32 +134,3 @@ If the new output is rendered within 200ms, users won't see the UI movement. Code cell outputs and markdown cells are rendered in the webview, which are async in nature. In order to have the cell outputs and markdown previews rendered when users scroll to them, we send rendering requests of cells in the next viewport when it's idle. Thus scrolling downwards is smoother. However, we **don't** warmup the previous viewport as the cell height change of previous viewport might trigger the flickering of markdown cells in current viewport. Before we optimize this, do not do any warmup of cells before current viewport. - - - -## Focus Tracking - -* DOM focus tracker of notebook editor container - * focus on cell container - * focus on cell editor - * focus on cell status bar - * focus on cell/execution/output toolbar - * focus on find widget - * focus on an element in the webview/iframe - - [ ] Focus on notebook toolbar -* hasWebviewFocus - - -CSS -* `monaco-list.focus-within` for cell container/editor borders - - -* in `codecell` we update the `cell.focusMode` when rendering it and if it's not the focused element anymore, the focusMode is reverted to container - - -* cell editor focused and editor blur event happens - * if focused element is another cell, then change focusMode to container - * if focused element is find widget / notebookToolbar, don't change focusMode - * if focused element is webview/iframe, don't change focusMode - * if focused element is outside of the notebook, don't change focusMode -* cell editor focused and cell moves out of view (no blur event) / `CodeCell.dispose` diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 19d614d3908..79d2f6f2f64 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -249,9 +249,11 @@ export interface ICellViewModel extends IGenericCellViewModel { readonly model: NotebookCellTextModel; readonly id: string; readonly textBuffer: IReadonlyTextBuffer; - readonly layoutInfo: { totalHeight: number; }; + readonly layoutInfo: { totalHeight: number; bottomToolbarOffset: number; editorWidth: number; }; readonly onDidChangeLayout: Event<{ totalHeight?: boolean | number; outerWidth?: number; }>; readonly onDidChangeCellStatusBarItems: Event; + readonly onCellDecorationsChanged: Event<{ added: INotebookCellDecorationOptions[], removed: INotebookCellDecorationOptions[] }>; + readonly onDidChangeState: Event; readonly editStateSource: string; readonly editorAttached: boolean; isInputCollapsed: boolean; @@ -398,7 +400,8 @@ export interface INotebookEditor { * An event emitted when the model of this editor has changed. */ readonly onDidChangeModel: Event; - readonly onDidFocusEditorWidget: Event; + readonly onDidFocusWidget: Event; + readonly onDidBlurWidget: Event; readonly onDidScroll: Event; readonly onDidChangeActiveCell: Event; readonly onDidChangeActiveKernel: Event; @@ -434,10 +437,15 @@ export interface INotebookEditor { getSelectionViewModels(): ICellViewModel[]; /** - * Focus the notebook editor cell list + * Focus the active cell in notebook cell list */ focus(): void; + /** + * Focus the notebook cell list container + */ + focusContainer(): void; + hasEditorFocus(): boolean; hasWebviewFocus(): boolean; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index b030a953c2f..67683ca4f6e 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -117,10 +117,6 @@ export class NotebookEditor extends EditorPane { protected createEditor(parent: HTMLElement): void { this._rootElement = DOM.append(parent, DOM.$('.notebook-editor')); - - // this._widget.createEditor(); - this._register(this.onDidFocus(() => this._widget.value?.updateEditorFocus())); - this._register(this.onDidBlur(() => this._widget.value?.updateEditorFocus())); } getDomNode() { @@ -179,8 +175,6 @@ export class NotebookEditor extends EditorPane { this.inputListener.value = input.onDidChangeCapabilities(() => this.onDidChangeInputCapabilities(input)); - this._saveEditorViewState(this.input); - this._widgetDisposableStore.clear(); // there currently is a widget which we still own so @@ -229,8 +223,8 @@ export class NotebookEditor extends EditorPane { await this._widget.value!.setModel(model.notebook, viewState); const isReadOnly = input.hasCapability(EditorInputCapabilities.Readonly); await this._widget.value!.setOptions({ ...options, isReadOnly }); - this._widgetDisposableStore.add(this._widget.value!.onDidFocus(() => this._onDidFocusWidget.fire())); - this._widgetDisposableStore.add(this._widget.value!.onDidBlur(() => this._onDidBlurWidget.fire())); + this._widgetDisposableStore.add(this._widget.value!.onDidFocusWidget(() => this._onDidFocusWidget.fire())); + this._widgetDisposableStore.add(this._widget.value!.onDidBlurWidget(() => this._onDidBlurWidget.fire())); this._widgetDisposableStore.add(this._editorDropService.createEditorDropTarget(this._widget.value!.getDomNode(), { containsGroup: (group) => this.group?.id === group.id diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 994640eb7fa..5bcbb759b23 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -47,9 +47,9 @@ import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/no import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList'; import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; import { BackLayerWebView, INotebookWebviewMessage } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView'; -import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys'; -import { CellDragAndDropController } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellDnd'; -import { CodeCellRenderer, ListTopCellToolbar, MarkupCellRenderer, NotebookCellListDelegate } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer'; +import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys'; +import { CellDragAndDropController } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd'; +import { CodeCellRenderer, MarkupCellRenderer, NotebookCellListDelegate } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { NotebookEventDispatcher } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel'; @@ -70,6 +70,7 @@ import { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; import { registerZIndex, ZIndex } from 'vs/platform/layout/browser/zIndexRegistry'; import { INotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; import { notebookDebug } from 'vs/workbench/contrib/notebook/browser/notebookLogger'; +import { ListTopCellToolbar } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar'; const $ = DOM.$; @@ -222,6 +223,41 @@ export function getDefaultNotebookCreationOptions() { }; } +class NotebookEditorWidgetFocusTracker extends Disposable { + + private _hasFocus: boolean; + private readonly _domFocusTracker: DOM.IFocusTracker; + + private readonly _onChange: Emitter = this._register(new Emitter()); + public readonly onChange: Event = this._onChange.event; + + constructor(domElement: HTMLElement) { + super(); + + this._hasFocus = false; + this._domFocusTracker = this._register(DOM.trackFocus(domElement)); + + this._register(this._domFocusTracker.onDidFocus(() => { + this._hasFocus = true; + this._onChange.fire(undefined); + })); + this._register(this._domFocusTracker.onDidBlur(() => { + this._hasFocus = false; + this._onChange.fire(undefined); + })); + } + + public hasFocus(): boolean { + return this._hasFocus; + } + + public refreshState(): void { + if (this._domFocusTracker.refreshState) { + this._domFocusTracker.refreshState(); + } + } +} + export class NotebookEditorWidget extends Disposable implements INotebookEditorDelegate { //#region Eventing private readonly _onDidChangeCellState = this._register(new Emitter()); @@ -240,12 +276,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD readonly onDidChangeSelection: Event = this._onDidChangeSelection.event; private readonly _onDidChangeVisibleRanges = this._register(new Emitter()); readonly onDidChangeVisibleRanges: Event = this._onDidChangeVisibleRanges.event; - private readonly _onDidFocusEditorWidget = this._register(new Emitter()); - readonly onDidFocusEditorWidget = this._onDidFocusEditorWidget.event; private readonly _onDidFocusEmitter = this._register(new Emitter()); - readonly onDidFocus = this._onDidFocusEmitter.event; + readonly onDidFocusWidget = this._onDidFocusEmitter.event; private readonly _onDidBlurEmitter = this._register(new Emitter()); - readonly onDidBlur = this._onDidBlurEmitter.event; + readonly onDidBlurWidget = this._onDidBlurEmitter.event; private readonly _onDidChangeActiveEditor = this._register(new Emitter()); readonly onDidChangeActiveEditor: Event = this._onDidChangeActiveEditor.event; private readonly _onDidChangeActiveKernel = this._register(new Emitter()); @@ -293,6 +327,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD private _cellContextKeyManager: CellContextKeyManager | null = null; private _isVisible = false; private readonly _uuid = generateUuid(); + private _widgetFocusTracker!: NotebookEditorWidgetFocusTracker; private _webviewFocused: boolean = false; private _isDisposed: boolean = false; @@ -581,6 +616,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD private _createBody(parent: HTMLElement): void { this._notebookTopToolbarContainer = document.createElement('div'); this._notebookTopToolbarContainer.classList.add('notebook-toolbar-container'); + this._notebookTopToolbarContainer.tabIndex = 0; this._notebookTopToolbarContainer.style.display = 'none'; DOM.append(parent, this._notebookTopToolbarContainer); this._body = document.createElement('div'); @@ -724,6 +760,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD // boder should always show styleSheets.push(` + .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-inner-container.cell-output-focus .cell-focus-indicator-left:before, .monaco-workbench .notebookOverlay .monaco-list:focus-within .monaco-list-row.focused .cell-inner-container .cell-focus-indicator-left:before { border-color: var(--notebook-focused-cell-border-color) !important; } @@ -1000,10 +1037,18 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } })); - const widgetFocusTracker = DOM.trackFocus(this.getDomNode()); - this._register(widgetFocusTracker); - this._register(widgetFocusTracker.onDidFocus(() => this._onDidFocusEmitter.fire())); - this._register(widgetFocusTracker.onDidBlur(() => this._onDidBlurEmitter.fire())); + this._widgetFocusTracker = this._register(new NotebookEditorWidgetFocusTracker(this.getDomNode())); + this._register(this._widgetFocusTracker.onChange(() => { + const focused = this._widgetFocusTracker.hasFocus(); + this._editorFocus.set(focused); + this.viewModel?.setEditorFocus(focused); + + if (focused) { + this._onDidFocusEmitter.fire(); + } else { + this._onDidBlurEmitter.fire(); + } + })); this._registerNotebookActionsToolbar(); } @@ -1121,7 +1166,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD const focused = this._list.getFocusedElements()[0]; if (focused) { if (!this._cellContextKeyManager) { - this._cellContextKeyManager = this._localStore.add(new CellContextKeyManager(this.scopedContextKeyService, this, focused as CellViewModel)); + this._cellContextKeyManager = this._localStore.add(this.instantiationService.createInstance(CellContextKeyManager, this, focused as CellViewModel)); } this._cellContextKeyManager.updateForElement(focused as CellViewModel); @@ -1251,11 +1296,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._localStore.add(this._webview.webview.onDidFocus(() => { this._outputFocus.set(true); this.updateEditorFocus(); - this._onDidFocusEmitter.fire(); - - if (this._overlayContainer.contains(document.activeElement)) { - this._webviewFocused = true; - } + this._webviewFocused = true; })); this._localStore.add(this._webview.onMessage(e => { @@ -1446,6 +1487,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD private _lastCellWithEditorFocus: ICellViewModel | null = null; private _validateCellFocusMode(cell: ICellViewModel) { + if (cell.focusMode !== CellFocusMode.Editor) { + return; + } + if (this._lastCellWithEditorFocus && this._lastCellWithEditorFocus !== cell) { this._lastCellWithEditorFocus.focusMode = CellFocusMode.Container; } @@ -1720,15 +1765,20 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD if (element && element.focusMode === CellFocusMode.Editor) { element.updateEditState(CellEditState.Editing, 'editorWidget.focus'); element.focusMode = CellFocusMode.Editor; - this._onDidFocusEditorWidget.fire(); return; } } this._list.domFocus(); } + } - this._onDidFocusEditorWidget.fire(); + focusContainer() { + if (this._webviewFocused) { + this._webview?.focusWebview(); + } else { + this._list.focusContainer(); + } } onWillHide() { @@ -1742,7 +1792,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD updateEditorFocus() { // Note - focus going to the webview will fire 'blur', but the webview element will be // a descendent of the notebook editor root. - const focused = this._overlayContainer.contains(document.activeElement); + this._widgetFocusTracker.refreshState(); + const focused = this._widgetFocusTracker.hasFocus(); this._editorFocus.set(focused); this.viewModel?.setEditorFocus(focused); } @@ -1751,7 +1802,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD // _editorFocus is driven by the FocusTracker, which is only guaranteed to _eventually_ fire blur. // If we need to know whether we have focus at this instant, we need to check the DOM manually. this.updateEditorFocus(); - return this._editorFocus.get() || false; + return this._widgetFocusTracker.hasFocus(); } hasWebviewFocus() { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 6f309a54847..ff39f09416d 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -320,6 +320,7 @@ export class NotebookOutputRendererInfoStore { const preferred = notebookProviderInfo && this.preferredMimetype.getValue()[notebookProviderInfo.id]?.[mimeType]; const notebookExtId = notebookProviderInfo?.extension?.value; + const notebookId = notebookProviderInfo?.id; const renderers: { ordered: IOrderedMimeType, score: number }[] = Array.from(this.contributedRenderers.values()) .map(renderer => { const ownScore = kernelProvides === undefined @@ -333,7 +334,7 @@ export class NotebookOutputRendererInfoStore { const rendererExtId = renderer.extensionId.value; const reuseScore = preferred === renderer.id ? ReuseOrder.PreviouslySelected - : rendererExtId === notebookExtId || RENDERER_EQUIVALENT_EXTENSIONS.get(rendererExtId)?.has(notebookExtId!) + : rendererExtId === notebookExtId || RENDERER_EQUIVALENT_EXTENSIONS.get(rendererExtId)?.has(notebookId!) ? ReuseOrder.SameExtensionAsNotebook : renderer.isBuiltin ? ReuseOrder.BuiltIn : ReuseOrder.OtherRenderer; return { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellActionView.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts similarity index 100% rename from src/vs/workbench/contrib/notebook/browser/view/renderers/cellActionView.ts rename to src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts similarity index 90% rename from src/vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys.ts rename to src/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts index f4bcf8830b8..976e22e8bc4 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts @@ -5,7 +5,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { CellEditState, CellFocusMode, CellViewModelStateChangeEvent, INotebookEditorDelegate, NotebookCellExecutionStateContext, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LINE_NUMBERS, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_TYPE } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, CellFocusMode, CellViewModelStateChangeEvent, ICellViewModel, INotebookEditorDelegate, NotebookCellExecutionStateContext, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LINE_NUMBERS, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_TYPE } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel'; import { NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -28,9 +28,9 @@ export class CellContextKeyManager extends Disposable { private readonly elementDisposables = this._register(new DisposableStore()); constructor( - private readonly contextKeyService: IContextKeyService, private readonly notebookEditor: INotebookEditorDelegate, - private element: CodeCellViewModel | MarkupCellViewModel + private element: ICellViewModel, + @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { super(); @@ -51,7 +51,7 @@ export class CellContextKeyManager extends Disposable { }); } - public updateForElement(element: MarkupCellViewModel | CodeCellViewModel) { + public updateForElement(element: ICellViewModel) { this.elementDisposables.clear(); this.elementDisposables.add(element.onDidChangeState(e => this.onDidChangeState(e))); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDecorations.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDecorations.ts new file mode 100644 index 00000000000..d2f8fe10881 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDecorations.ts @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as DOM from 'vs/base/browser/dom'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; + +export class CellDecorations extends Disposable { + constructor( + rootContainer: HTMLElement, + decorationContainer: HTMLElement, + element: ICellViewModel + ) { + super(); + + const removedClassNames: string[] = []; + rootContainer.classList.forEach(className => { + if (/^nb\-.*$/.test(className)) { + removedClassNames.push(className); + } + }); + + removedClassNames.forEach(className => { + rootContainer.classList.remove(className); + }); + + decorationContainer.innerText = ''; + + const generateCellTopDecorations = () => { + decorationContainer.innerText = ''; + + element.getCellDecorations().filter(options => options.topClassName !== undefined).forEach(options => { + decorationContainer.append(DOM.$(`.${options.topClassName!}`)); + }); + }; + + this._register(element.onCellDecorationsChanged((e) => { + const modified = e.added.find(e => e.topClassName) || e.removed.find(e => e.topClassName); + + if (modified) { + generateCellTopDecorations(); + } + })); + + generateCellTopDecorations(); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellDnd.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts similarity index 96% rename from src/vs/workbench/contrib/notebook/browser/view/renderers/cellDnd.ts rename to src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts index c25b8fc76c3..d69b228395a 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellDnd.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts @@ -15,8 +15,8 @@ import { cellRangesToIndexes, ICellRange } from 'vs/workbench/contrib/notebook/c const $ = DOM.$; -export const DRAGGING_CLASS = 'cell-dragging'; -export const GLOBAL_DRAG_CLASS = 'global-drag-active'; +const DRAGGING_CLASS = 'cell-dragging'; +const GLOBAL_DRAG_CLASS = 'global-drag-active'; type DragImageProvider = () => HTMLElement; @@ -97,6 +97,14 @@ export class CellDragAndDropController extends Disposable { }); } + renderElement(element: ICellViewModel, templateData: BaseCellRenderTemplate): void { + if (element.dragging) { + templateData.container.classList.add(DRAGGING_CLASS); + } else { + templateData.container.classList.remove(DRAGGING_CLASS); + } + } + private setInsertIndicatorVisibility(visible: boolean) { this.listInsertionIndicator.style.opacity = visible ? '1' : '0'; } @@ -305,7 +313,7 @@ export class CellDragAndDropController extends Disposable { const container = templateData.container; dragHandle.setAttribute('draggable', 'true'); - templateData.disposables.add(DOM.addDisposableListener(dragHandle, DOM.EventType.DRAG_END, () => { + templateData.templateDisposables.add(DOM.addDisposableListener(dragHandle, DOM.EventType.DRAG_END, () => { if (!this.notebookEditor.notebookOptions.getLayoutConfiguration().dragAndDropEnabled || !!this.notebookEditor.isReadOnly) { return; } @@ -315,7 +323,7 @@ export class CellDragAndDropController extends Disposable { this.dragCleanup(); })); - templateData.disposables.add(DOM.addDisposableListener(dragHandle, DOM.EventType.DRAG_START, event => { + templateData.templateDisposables.add(DOM.addDisposableListener(dragHandle, DOM.EventType.DRAG_START, event => { if (!event.dataTransfer) { return; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellEditorOptions.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts similarity index 92% rename from src/vs/workbench/contrib/notebook/browser/view/renderers/cellEditorOptions.ts rename to src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts index c819584f797..9117c436853 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellEditorOptions.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { deepClone } from 'vs/base/common/objects'; import { IEditorOptions, LineNumbersType } from 'vs/editor/common/config/editorOptions'; import { localize } from 'vs/nls'; @@ -16,13 +16,13 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { Registry } from 'vs/platform/registry/common/platform'; import { ActiveEditorContext } from 'vs/workbench/common/editor'; import { INotebookCellToolbarActionContext, INotebookCommandContext, NotebookMultiCellAction, NOTEBOOK_ACTIONS_CATEGORY } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; -import { ICellViewModel, INotebookEditorDelegate, NOTEBOOK_CELL_LINE_NUMBERS, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellViewModelStateChangeEvent, ICellViewModel, INotebookEditorDelegate, NOTEBOOK_CELL_LINE_NUMBERS, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; +import { CellPart } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellPart'; import { NotebookCellInternalMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions'; -export class CellEditorOptions extends Disposable { - +export class CellEditorOptions extends CellPart { private static fixedEditorOptions: IEditorOptions = { scrollBeyondLastLine: false, scrollbar: { @@ -86,6 +86,24 @@ export class CellEditorOptions extends Disposable { this._value = this._computeEditorOptions(); } + + renderCell(element: ICellViewModel): void { + // no op + } + + prepareLayout(): void { + // nothing to read + } + updateLayoutNow(element: ICellViewModel): void { + // nothing to update + } + + updateState(element: ICellViewModel, e: CellViewModelStateChangeEvent) { + if (e.cellLineNumberChanged) { + this.setLineNumbers(element.lineNumbers); + } + } + private _recomputeOptions(): void { this._value = this._computeEditorOptions(); this._onDidChange.fire(); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellFocusIndicator.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellFocusIndicator.ts new file mode 100644 index 00000000000..4c54c4ab158 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellFocusIndicator.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// import * as DOM from 'vs/base/browser/dom'; +import { FastDomNode } from 'vs/base/browser/fastDomNode'; +import { CellViewModelStateChangeEvent, ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellPart } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellPart'; +import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; + +export class CellFocusIndicator extends CellPart { + constructor( + readonly notebookEditor: INotebookEditorDelegate, + readonly top: FastDomNode, + readonly left: FastDomNode, + readonly right: FastDomNode, + readonly bottom: FastDomNode + ) { + super(); + } + + renderCell(element: ICellViewModel): void { + // no op + } + + prepareLayout(): void { + // nothing to read + } + + updateLayoutNow(element: ICellViewModel): void { + if (element.cellKind === CellKind.Markup) { + // markdown cell + const indicatorPostion = this.notebookEditor.notebookOptions.computeIndicatorPosition(element.layoutInfo.totalHeight, this.notebookEditor.textModel?.viewType); + this.bottom.domNode.style.transform = `translateY(${indicatorPostion.bottomIndicatorTop}px)`; + this.left.setHeight(indicatorPostion.verticalIndicatorHeight); + this.right.setHeight(indicatorPostion.verticalIndicatorHeight); + } else { + // code cell + const cell = element as CodeCellViewModel; + const layoutInfo = this.notebookEditor.notebookOptions.getLayoutConfiguration(); + const bottomToolbarDimensions = this.notebookEditor.notebookOptions.computeBottomToolbarDimensions(this.notebookEditor.textModel?.viewType); + this.left.setHeight(cell.layoutInfo.indicatorHeight); + this.right.setHeight(cell.layoutInfo.indicatorHeight); + this.bottom.domNode.style.transform = `translateY(${cell.layoutInfo.totalHeight - bottomToolbarDimensions.bottomToolbarGap - layoutInfo.cellBottomMargin}px)`; + } + } + + updateState(element: ICellViewModel, e: CellViewModelStateChangeEvent): void { + // nothing to update + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts similarity index 96% rename from src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts rename to src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts index df62bb4b569..2590f1251b1 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts @@ -11,7 +11,6 @@ import { Action, IAction } from 'vs/base/common/actions'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { MarshalledId } from 'vs/base/common/marshalling'; -import { Schemas } from 'vs/base/common/network'; import * as nls from 'vs/nls'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; @@ -25,10 +24,10 @@ import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ViewContainerLocation } from 'vs/workbench/common/views'; import { IExtensionsViewPaneContainer, VIEWLET_ID as EXTENSION_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; -import { ICellOutputViewModel, ICellViewModel, IInsetRenderOutput, INotebookEditorDelegate, IRenderOutput, JUPYTER_EXTENSION_ID, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellViewModelStateChangeEvent, ICellOutputViewModel, ICellViewModel, IInsetRenderOutput, INotebookEditorDelegate, IRenderOutput, JUPYTER_EXTENSION_ID, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { mimetypeIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { CodeCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; -import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets'; +import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellWidgets'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { BUILTIN_RENDERER_ID, CellUri, IOrderedMimeType, NotebookCellOutputsSplice, RENDERER_NOT_AVAILABLE } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -36,6 +35,7 @@ import { INotebookKernel } from 'vs/workbench/contrib/notebook/common/notebookKe import { OutputInnerContainerTopPadding } from 'vs/workbench/contrib/notebook/common/notebookOptions'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; +import { CellPart } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellPart'; interface IMimeTypeRenderer extends IQuickPickItem { index: number; @@ -557,7 +557,7 @@ class OutputEntryViewHandler { } } -export class CellOutputContainer extends Disposable { +export class CellOutputContainer extends CellPart { private _outputEntries: OutputEntryViewHandler[] = []; get renderedOutputEntries() { @@ -579,17 +579,25 @@ export class CellOutputContainer extends Disposable { })); this._register(viewCell.onDidChangeLayout(() => { - this._outputEntries.forEach(entry => { - const index = viewCell.outputsViewModels.indexOf(entry.model); - if (index >= 0) { - const top = this.viewCell.getOutputOffsetInContainer(index); - entry.element.updateDOMTop(top); - } - }); + this.updateLayoutNow(viewCell); })); } - probeHeight() { + renderCell(element: ICellViewModel): void { + // no op + } + + updateLayoutNow(viewCell: CodeCellViewModel) { + this._outputEntries.forEach(entry => { + const index = this.viewCell.outputsViewModels.indexOf(entry.model); + if (index >= 0) { + const top = this.viewCell.getOutputOffsetInContainer(index); + entry.element.updateDOMTop(top); + } + }); + } + + prepareLayout() { this._outputEntries.forEach(entry => { const index = this.viewCell.outputsViewModels.indexOf(entry.model); if (index >= 0) { @@ -598,6 +606,11 @@ export class CellOutputContainer extends Disposable { }); } + + updateState(element: ICellViewModel, e: CellViewModelStateChangeEvent): void { + // nothing to update + } + render(editorHeight: number) { if (this.viewCell.outputsViewModels.length > 0) { if (this.viewCell.layoutInfo.totalHeight !== 0 && this.viewCell.layoutInfo.editorHeight > editorHeight) { @@ -630,7 +643,7 @@ export class CellOutputContainer extends Disposable { this.templateData.outputShowMoreContainer.domNode.innerText = ''; if (this.viewCell.outputsViewModels.length > this.options.limit) { - this.templateData.outputShowMoreContainer.domNode.appendChild(this._generateShowMoreElement(this.templateData.disposables)); + this.templateData.outputShowMoreContainer.domNode.appendChild(this._generateShowMoreElement(this.templateData.templateDisposables)); } else { DOM.hide(this.templateData.outputShowMoreContainer.domNode); this.viewCell.updateOutputShowMoreContainerHeight(0); @@ -835,7 +848,7 @@ export class CellOutputContainer extends Disposable { if (this.viewCell.outputsViewModels.length > this.options.limit) { DOM.show(this.templateData.outputShowMoreContainer.domNode); if (!this.templateData.outputShowMoreContainer.domNode.hasChildNodes()) { - this.templateData.outputShowMoreContainer.domNode.appendChild(this._generateShowMoreElement(this.templateData.disposables)); + this.templateData.outputShowMoreContainer.domNode.appendChild(this._generateShowMoreElement(this.templateData.templateDisposables)); } this.viewCell.updateOutputShowMoreContainerHeight(46); } else { @@ -862,7 +875,7 @@ export class CellOutputContainer extends Disposable { actionHandler: { callback: (content) => { if (content === 'command:workbench.action.openLargeOutput') { - this.openerService.open(CellUri.generateCellUri(this.notebookEditor.textModel!.uri, this.viewCell.handle, Schemas.vscodeNotebookCellOutput)); + this.openerService.open(CellUri.generateCellOutputUri(this.notebookEditor.textModel!.uri, this.viewCell.handle)); } return; diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellPart.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellPart.ts new file mode 100644 index 00000000000..6a2ca9bc9b8 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellPart.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { CellViewModelStateChangeEvent, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { BaseCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; + +export abstract class CellPart extends Disposable { + constructor() { + super(); + } + + /** + * Update the DOM for the cell `element` + */ + abstract renderCell(element: ICellViewModel, templateData: BaseCellRenderTemplate): void; + + /** + * Perform DOM read operations to prepare for the list/cell layout update. + */ + abstract prepareLayout(): void; + + /** + * Update DOM per cell layout info change + */ + abstract updateLayoutNow(element: ICellViewModel): void; + + /** + * Update per cell state change + */ + abstract updateState(element: ICellViewModel, e: CellViewModelStateChangeEvent): void; +} diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellProgressBar.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellProgressBar.ts new file mode 100644 index 00000000000..fb061f811d2 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellProgressBar.ts @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; +import { ICellViewModel, CellViewModelStateChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellPart } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellPart'; +import { NotebookCellExecutionState, NotebookCellInternalMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; + +export class CellProgressBar extends CellPart { + private readonly _progressBar: ProgressBar; + private readonly _collapsedProgressBar: ProgressBar; + + constructor( + editorContainer: HTMLElement, + collapsedInputContainer: HTMLElement) { + super(); + + this._progressBar = this._register(new ProgressBar(editorContainer)); + this._progressBar.hide(); + + this._collapsedProgressBar = this._register(new ProgressBar(collapsedInputContainer)); + this._collapsedProgressBar.hide(); + } + + renderCell(element: ICellViewModel): void { + this.updateForInternalMetadata(element, element.internalMetadata); + } + + prepareLayout(): void { + // nothing to read + } + + updateLayoutNow(element: ICellViewModel): void { + // nothing to update + } + + updateState(element: ICellViewModel, e: CellViewModelStateChangeEvent): void { + if (e.metadataChanged || e.internalMetadataChanged) { + this.updateForInternalMetadata(element, element.internalMetadata); + } + + if (e.inputCollapsedChanged) { + if (element.isInputCollapsed) { + this._progressBar.hide(); + if (element.internalMetadata.runState === NotebookCellExecutionState.Executing) { + showProgressBar(this._collapsedProgressBar); + } + } else { + this._collapsedProgressBar.hide(); + if (element.internalMetadata.runState === NotebookCellExecutionState.Executing) { + showProgressBar(this._progressBar); + } + } + } + } + + updateForInternalMetadata(element: ICellViewModel, internalMetadata: NotebookCellInternalMetadata): void { + const progressBar = element.isInputCollapsed ? this._collapsedProgressBar : this._progressBar; + if (internalMetadata.runState === NotebookCellExecutionState.Executing && !internalMetadata.isPaused) { + showProgressBar(progressBar); + } else { + progressBar.hide(); + } + } +} + +function showProgressBar(progressBar: ProgressBar): void { + progressBar.infinite().show(500); +} diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts new file mode 100644 index 00000000000..55228eb4a14 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts @@ -0,0 +1,249 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as DOM from 'vs/base/browser/dom'; +import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; +import { IAction } from 'vs/base/common/actions'; +import { disposableTimeout } from 'vs/base/common/async'; +import { Emitter, Event } from 'vs/base/common/event'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { MarshalledId } from 'vs/base/common/marshalling'; +import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { createActionViewItem, createAndFillInActionBarActions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenu, IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; +import { DeleteCellAction } from 'vs/workbench/contrib/notebook/browser/controller/editActions'; +import { CellViewModelStateChangeEvent, ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView'; +import { CellPart } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellPart'; +import { BaseCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; + +export class BetweenCellToolbar extends CellPart { + private _betweenCellToolbar!: ToolBar; + + constructor( + private readonly _notebookEditor: INotebookEditorDelegate, + _titleToolbarContainer: HTMLElement, + private readonly _bottomCellToolbarContainer: HTMLElement, + @IInstantiationService instantiationService: IInstantiationService, + @IContextMenuService contextMenuService: IContextMenuService, + @IContextKeyService contextKeyService: IContextKeyService, + @IMenuService menuService: IMenuService + ) { + super(); + + this._betweenCellToolbar = this._register(new ToolBar(this._bottomCellToolbarContainer, contextMenuService, { + actionViewItemProvider: action => { + if (action instanceof MenuItemAction) { + if (this._notebookEditor.notebookOptions.getLayoutConfiguration().insertToolbarAlignment === 'center') { + return instantiationService.createInstance(CodiconActionViewItem, action); + } else { + return instantiationService.createInstance(MenuEntryActionViewItem, action, undefined); + } + } + + return undefined; + } + })); + + const menu = this._register(menuService.createMenu(this._notebookEditor.creationOptions.menuIds.cellInsertToolbar, contextKeyService)); + const updateActions = () => { + const actions = getCellToolbarActions(menu); + this._betweenCellToolbar.setActions(actions.primary, actions.secondary); + }; + + this._register(menu.onDidChange(() => updateActions())); + this._register(this._notebookEditor.notebookOptions.onDidChangeOptions((e) => { + if (e.insertToolbarAlignment) { + updateActions(); + } + })); + updateActions(); + } + + updateContext(context: INotebookCellActionContext) { + this._betweenCellToolbar.context = context; + } + + renderCell(element: ICellViewModel, templateData: BaseCellRenderTemplate): void { + this._betweenCellToolbar.context = { + ui: true, + cell: element, + cellTemplate: templateData, + notebookEditor: this._notebookEditor, + $mid: MarshalledId.NotebookCellActionContext + }; + } + + prepareLayout(): void { + // nothing to read + } + + updateLayoutNow(element: ICellViewModel) { + const bottomToolbarOffset = element.layoutInfo.bottomToolbarOffset; + this._bottomCellToolbarContainer.style.transform = `translateY(${bottomToolbarOffset}px)`; + } + + + updateState(element: ICellViewModel, e: CellViewModelStateChangeEvent): void { + // nothing to update + } +} + + +export interface ICssClassDelegate { + toggle: (className: string, force?: boolean) => void; +} + +export class CellTitleToolbarPart extends CellPart { + private _toolbar: ToolBar; + private _deleteToolbar: ToolBar; + private _titleMenu: IMenu; + private _actionsDisposables = this._register(new DisposableStore()); + + private _hasActions = false; + private readonly _onDidUpdateActions: Emitter = this._register(new Emitter()); + readonly onDidUpdateActions: Event = this._onDidUpdateActions.event; + + get hasActions(): boolean { + return this._hasActions; + } + + constructor( + toolbarContainer: HTMLElement, + private readonly _rootClassDelegate: ICssClassDelegate, + toolbarId: MenuId, + private readonly _notebookEditor: INotebookEditorDelegate, + @IContextKeyService contextKeyService: IContextKeyService, + @IMenuService menuService: IMenuService, + @IInstantiationService instantiationService: IInstantiationService, + ) { + super(); + + this._toolbar = instantiationService.invokeFunction(accessor => createToolbar(accessor, toolbarContainer)); + this._titleMenu = this._register(menuService.createMenu(toolbarId, contextKeyService)); + + this._deleteToolbar = this._register(instantiationService.invokeFunction(accessor => createToolbar(accessor, toolbarContainer, 'cell-delete-toolbar'))); + if (!this._notebookEditor.creationOptions.isReadOnly) { + this._deleteToolbar.setActions([instantiationService.createInstance(DeleteCellAction)]); + } + + this.setupChangeListeners(); + } + + renderCell(element: ICellViewModel, templateData: BaseCellRenderTemplate): void { + this.updateContext({ + ui: true, + cell: element, + cellTemplate: templateData, + notebookEditor: this._notebookEditor, + $mid: MarshalledId.NotebookCellActionContext + }); + } + + prepareLayout(): void { + // nothing to read + } + updateLayoutNow(element: ICellViewModel): void { + // no op + } + updateState(element: ICellViewModel, e: CellViewModelStateChangeEvent): void { + // no op + } + + private updateContext(toolbarContext: INotebookCellActionContext) { + this._toolbar.context = toolbarContext; + this._deleteToolbar.context = toolbarContext; + } + + private setupChangeListeners(): void { + // #103926 + let dropdownIsVisible = false; + let deferredUpdate: (() => void) | undefined; + + this.updateActions(); + this._register(this._titleMenu.onDidChange(() => { + if (dropdownIsVisible) { + deferredUpdate = () => this.updateActions(); + return; + } + + this.updateActions(); + })); + this._rootClassDelegate.toggle('cell-toolbar-dropdown-active', false); + this._register(this._toolbar.onDidChangeDropdownVisibility(visible => { + dropdownIsVisible = visible; + this._rootClassDelegate.toggle('cell-toolbar-dropdown-active', visible); + + if (deferredUpdate && !visible) { + this._register(disposableTimeout(() => { + if (deferredUpdate) { + deferredUpdate(); + } + })); + + deferredUpdate = undefined; + } + })); + } + + private updateActions() { + this._actionsDisposables.clear(); + const actions = getCellToolbarActions(this._titleMenu); + this._actionsDisposables.add(actions.disposable); + + const hadFocus = DOM.isAncestor(document.activeElement, this._toolbar.getElement()); + this._toolbar.setActions(actions.primary, actions.secondary); + if (hadFocus) { + this._notebookEditor.focus(); + } + + if (actions.primary.length || actions.secondary.length) { + this._rootClassDelegate.toggle('cell-has-toolbar-actions', true); + this._hasActions = true; + this._onDidUpdateActions.fire(); + } else { + this._rootClassDelegate.toggle('cell-has-toolbar-actions', false); + this._hasActions = false; + this._onDidUpdateActions.fire(); + } + } +} + +function getCellToolbarActions(menu: IMenu): { primary: IAction[], secondary: IAction[]; disposable: IDisposable; } { + const primary: IAction[] = []; + const secondary: IAction[] = []; + const result = { primary, secondary }; + + const disposable = createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, result, g => /^inline/.test(g)); + + return { + ...result, + disposable + }; +} + +function createToolbar(accessor: ServicesAccessor, container: HTMLElement, elementClass?: string): ToolBar { + const contextMenuService = accessor.get(IContextMenuService); + const keybindingService = accessor.get(IKeybindingService); + const instantiationService = accessor.get(IInstantiationService); + const toolbar = new ToolBar(container, contextMenuService, { + getKeyBinding: action => keybindingService.lookupKeybinding(action.id), + actionViewItemProvider: action => { + return createActionViewItem(instantiationService, action); + }, + renderDropdownAsChildElement: true + }); + + if (elementClass) { + toolbar.getElement().classList.add(elementClass); + } + + return toolbar; +} diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellWidgets.ts similarity index 89% rename from src/vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets.ts rename to src/vs/workbench/contrib/notebook/browser/view/cellParts/cellWidgets.ts index 1f4546456ad..a048e62a486 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellWidgets.ts @@ -12,6 +12,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { stripIcons } from 'vs/base/common/iconLabels'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; +import { MarshalledId } from 'vs/base/common/marshalling'; import { ElementSizeObserver } from 'vs/editor/browser/config/elementSizeObserver'; import { IDimension, isThemeColor } from 'vs/editor/common/editorCommon'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -20,7 +21,9 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService, ThemeColor } from 'vs/platform/theme/common/themeService'; import { INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; -import { CodeCellLayoutInfo, MarkdownCellLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellViewModelStateChangeEvent, ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellPart } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellPart'; +import { BaseCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; import { CellStatusbarAlignment, INotebookCellStatusBarItem } from 'vs/workbench/contrib/notebook/common/notebookCommon'; const $ = DOM.$; @@ -36,7 +39,7 @@ export const enum ClickTargetType { ContributedCommandItem = 2 } -export class CellEditorStatusBar extends Disposable { +export class CellEditorStatusBar extends CellPart { readonly statusBarContainer: HTMLElement; private readonly leftItemsContainer: HTMLElement; @@ -52,6 +55,7 @@ export class CellEditorStatusBar extends Disposable { readonly onDidClick: Event = this._onDidClick.event; constructor( + private readonly _notebookEditor: INotebookEditorDelegate, container: HTMLElement, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IThemeService private readonly _themeService: IThemeService, @@ -66,7 +70,7 @@ export class CellEditorStatusBar extends Disposable { this.itemsDisposable = this._register(new DisposableStore()); - this._register(this._themeService.onDidColorThemeChange(() => this.currentContext && this.update(this.currentContext))); + this._register(this._themeService.onDidColorThemeChange(() => this.currentContext && this.updateContext(this.currentContext))); this._register(DOM.addDisposableListener(this.statusBarContainer, DOM.EventType.CLICK, e => { if (e.target === leftItemsContainer || e.target === rightItemsContainer || e.target === this.statusBarContainer) { @@ -92,13 +96,23 @@ export class CellEditorStatusBar extends Disposable { })); } - private layout(): void { - if (!this.currentContext) { - return; - } - // TODO@roblou maybe more props should be in common layoutInfo? - const layoutInfo = this.currentContext.cell.layoutInfo as CodeCellLayoutInfo | MarkdownCellLayoutInfo; + renderCell(element: ICellViewModel, templateData: BaseCellRenderTemplate): void { + this.updateContext({ + ui: true, + cell: element, + cellTemplate: templateData, + notebookEditor: this._notebookEditor, + $mid: MarshalledId.NotebookCellActionContext + }); + } + + prepareLayout(): void { + // nothing to read + } + + updateLayoutNow(element: ICellViewModel): void { + const layoutInfo = element.layoutInfo; const width = layoutInfo.editorWidth; if (!width) { return; @@ -112,11 +126,16 @@ export class CellEditorStatusBar extends Disposable { this.rightItems.forEach(item => item.maxWidth = maxItemWidth); } + + updateState(element: ICellViewModel, e: CellViewModelStateChangeEvent): void { + // nothing to update + } + private getMaxItemWidth() { return this.width / 2; } - update(context: INotebookCellActionContext) { + updateContext(context: INotebookCellActionContext) { this.currentContext = context; this.itemsDisposable.clear(); @@ -124,10 +143,14 @@ export class CellEditorStatusBar extends Disposable { return; } - this.itemsDisposable.add(this.currentContext.cell.onDidChangeLayout(() => this.layout())); + this.itemsDisposable.add(this.currentContext.cell.onDidChangeLayout(() => { + if (this.currentContext) { + this.updateLayoutNow(this.currentContext.cell); + } + })); this.itemsDisposable.add(this.currentContext.cell.onDidChangeCellStatusBarItems(() => this.updateRenderedItems())); this.itemsDisposable.add(this.currentContext.notebookEditor.onDidChangeActiveCell(() => this.updateActiveCell())); - this.layout(); + this.updateLayoutNow(this.currentContext.cell); this.updateActiveCell(); this.updateRenderedItems(); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts similarity index 83% rename from src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts rename to src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts index cce8bfde294..0dca46ec29e 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts @@ -20,10 +20,11 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { CellFocusMode, EXPAND_CELL_INPUT_COMMAND_ID, IActiveNotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CodeCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; -import { CellOutputContainer } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellOutput'; -import { ClickTargetType } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets'; +import { CellOutputContainer } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput'; +import { ClickTargetType } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellWidgets'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; +import { CellPart } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellPart'; export class CodeCell extends Disposable { @@ -38,6 +39,7 @@ export class CodeCell extends Disposable { private readonly notebookEditor: IActiveNotebookEditorDelegate, private readonly viewCell: CodeCellViewModel, private readonly templateData: CodeCellRenderTemplate, + private readonly cellParts: CellPart[], @IInstantiationService private readonly instantiationService: IInstantiationService, @INotebookCellStatusBarService readonly notebookCellStatusBarService: INotebookCellStatusBarService, @IKeybindingService readonly keybindingService: IKeybindingService, @@ -48,13 +50,52 @@ export class CodeCell extends Disposable { const editorHeight = this.calculateInitEditorHeight(); this.initializeEditor(editorHeight); - this.registerEditorOptionsListener(); - this.registerViewCellStateChange(); - this.registerFocusModeTracker(); - this.registerEditorLayoutListeners(); + + this.registerViewCellLayoutChange(); + this.registerCellEditorEventListeners(); this.registerDecorations(); this.registerMouseListener(); + this._register(this.viewCell.onDidChangeState(e => { + this.cellParts.forEach(cellPart => { + cellPart.updateState(this.viewCell, e); + }); + + if (e.outputIsHoveredChanged) { + this.updateForOutputHover(); + } + + if (e.outputIsFocusedChanged) { + this.updateForOutputFocus(); + } + + if (e.metadataChanged || e.internalMetadataChanged) { + this.updateEditorOptions(); + } + + if (e.inputCollapsedChanged || e.outputCollapsedChanged) { + this.viewCell.pauseLayout(); + const updated = this.updateForCollapseState(); + this.viewCell.resumeLayout(); + if (updated) { + this.relayoutCell(); + } + } + + if (e.focusModeChanged) { + this.updateEditorForFocusModeChange(); + } + })); + + this.cellParts.forEach(cellPart => { + cellPart.renderCell(this.viewCell, this.templateData); + }); + + this.updateEditorOptions(); + this.updateEditorForFocusModeChange(); + this.updateForOutputHover(); + this.updateForOutputFocus(); + // Render Outputs this._outputContainerRenderer = this.instantiationService.createInstance(CellOutputContainer, notebookEditor, viewCell, templateData, { limit: 500 }); this._outputContainerRenderer.render(editorHeight); @@ -65,10 +106,22 @@ export class CodeCell extends Disposable { } this._register(this.viewCell.onLayoutInfoRead(() => { - this._outputContainerRenderer.probeHeight(); + this._outputContainerRenderer.prepareLayout(); + this.cellParts.forEach(cellPart => cellPart.prepareLayout()); })); this.updateForCollapseState(); + + this.updateForOutputs(); + this._register(viewCell.onDidChangeOutputs(_e => this.updateForOutputs())); + } + + private updateForOutputHover() { + this.templateData.container.classList.toggle('cell-output-hover', this.viewCell.outputIsHovered); + } + + private updateForOutputFocus() { + this.templateData.container.classList.toggle('cell-output-focus', this.viewCell.outputIsFocused); } private calculateInitEditorHeight() { @@ -121,41 +174,29 @@ export class CodeCell extends Disposable { }); } - private registerEditorOptionsListener() { - const updateEditorOptions = () => { - const editor = this.templateData.editor; - if (!editor) { - return; - } - - const isReadonly = this.notebookEditor.isReadOnly; - const padding = this.notebookEditor.notebookOptions.computeEditorPadding(this.viewCell.internalMetadata); - const options = editor.getOptions(); - if (options.get(EditorOption.readOnly) !== isReadonly || options.get(EditorOption.padding) !== padding) { - editor.updateOptions({ readOnly: this.notebookEditor.isReadOnly, padding: this.notebookEditor.notebookOptions.computeEditorPadding(this.viewCell.internalMetadata) }); - } - }; - - updateEditorOptions(); - this._register(this.viewCell.onDidChangeState((e) => { - if (e.metadataChanged || e.internalMetadataChanged) { - updateEditorOptions(); - } - })); + private updateForOutputs(): void { + if (this.viewCell.outputsViewModels.length) { + DOM.show(this.templateData.focusSinkElement); + } else { + DOM.hide(this.templateData.focusSinkElement); + } } - private registerViewCellStateChange() { - this._register(this.viewCell.onDidChangeState((e) => { - if (e.inputCollapsedChanged || e.outputCollapsedChanged) { - this.viewCell.pauseLayout(); - const updated = this.updateForCollapseState(); - this.viewCell.resumeLayout(); - if (updated) { - this.relayoutCell(); - } - } - })); + private updateEditorOptions() { + const editor = this.templateData.editor; + if (!editor) { + return; + } + const isReadonly = this.notebookEditor.isReadOnly; + const padding = this.notebookEditor.notebookOptions.computeEditorPadding(this.viewCell.internalMetadata); + const options = editor.getOptions(); + if (options.get(EditorOption.readOnly) !== isReadonly || options.get(EditorOption.padding) !== padding) { + editor.updateOptions({ readOnly: this.notebookEditor.isReadOnly, padding: this.notebookEditor.notebookOptions.computeEditorPadding(this.viewCell.internalMetadata) }); + } + } + + private registerViewCellLayoutChange() { this._register(this.viewCell.onDidChangeLayout((e) => { if (e.outerWidth !== undefined) { const layoutInfo = this.templateData.editor.getLayoutInfo(); @@ -170,7 +211,7 @@ export class CodeCell extends Disposable { })); } - private registerEditorLayoutListeners() { + private registerCellEditorEventListeners() { this._register(this.templateData.editor.onDidContentSizeChange((e) => { if (e.contentHeightChanged) { if (this.viewCell.layoutInfo.editorHeight !== e.contentHeight) { @@ -191,6 +232,28 @@ export class CodeCell extends Disposable { this.notebookEditor.revealLineInViewAsync(this.viewCell, primarySelection.positionLineNumber); } })); + + // Focus Mode + const updateFocusModeForEditorEvent = () => { + this.viewCell.focusMode = + (this.templateData.editor.hasWidgetFocus() || (document.activeElement && this.templateData.statusBar.statusBarContainer.contains(document.activeElement))) + ? CellFocusMode.Editor + : CellFocusMode.Container; + }; + + this._register(this.templateData.editor.onDidFocusEditorWidget(() => { + updateFocusModeForEditorEvent(); + })); + this._register(this.templateData.editor.onDidBlurEditorWidget(() => { + // this is for a special case: + // users click the status bar empty space, which we will then focus the editor + // so we don't want to update the focus state too eagerly, it will be updated with onDidFocusEditorWidget + if ( + this.notebookEditor.hasEditorFocus() && + !(document.activeElement && this.templateData.statusBar.statusBarContainer.contains(document.activeElement))) { + updateFocusModeForEditorEvent(); + } + })); } private registerDecorations() { @@ -249,43 +312,12 @@ export class CodeCell extends Disposable { })); } - private registerFocusModeTracker() { - const updateEditorForFocusModeChange = () => { - if (this.viewCell.focusMode === CellFocusMode.Editor && this.notebookEditor.getActiveCell() === this.viewCell) { - this.templateData.editor?.focus(); - } + private updateEditorForFocusModeChange() { + if (this.viewCell.focusMode === CellFocusMode.Editor && this.notebookEditor.getActiveCell() === this.viewCell) { + this.templateData.editor?.focus(); + } - this.templateData.container.classList.toggle('cell-editor-focus', this.viewCell.focusMode === CellFocusMode.Editor); - }; - this._register(this.viewCell.onDidChangeState((e) => { - if (e.focusModeChanged) { - updateEditorForFocusModeChange(); - } - })); - - updateEditorForFocusModeChange(); - - // Focus Mode - const updateFocusModeForEditorEvent = () => { - this.viewCell.focusMode = - (this.templateData.editor.hasWidgetFocus() || (document.activeElement && this.templateData.statusBar.statusBarContainer.contains(document.activeElement))) - ? CellFocusMode.Editor - : CellFocusMode.Container; - }; - - this._register(this.templateData.editor.onDidFocusEditorWidget(() => { - updateFocusModeForEditorEvent(); - })); - this._register(this.templateData.editor.onDidBlurEditorWidget(() => { - // this is for a special case: - // users click the status bar empty space, which we will then focus the editor - // so we don't want to update the focus state too eagerly, it will be updated with onDidFocusEditorWidget - if ( - this.notebookEditor.hasEditorFocus() && - !(document.activeElement && this.templateData.statusBar.statusBarContainer.contains(document.activeElement))) { - updateFocusModeForEditorEvent(); - } - })); + this.templateData.container.classList.toggle('cell-editor-focus', this.viewCell.focusMode === CellFocusMode.Editor); } private updateForCollapseState(): boolean { @@ -442,11 +474,16 @@ export class CodeCell extends Disposable { override dispose() { this._isDisposed = true; + // move focus back to the cell list otherwise the focus goes to body + if (this.notebookEditor.getActiveCell() === this.viewCell && this.viewCell.focusMode === CellFocusMode.Editor) { + this.notebookEditor.focusContainer(); + } + this.viewCell.detachTextEditor(); this._removeInputCollapsePreview(); this._outputContainerRenderer.dispose(); this._untrustedStatusItem?.dispose(); - this.templateData.focusIndicatorLeft.setHeight(0); + this.templateData.focusIndicator.left.setHeight(0); super.dispose(); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCellRunToolbar.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCellRunToolbar.ts new file mode 100644 index 00000000000..53b22067c70 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCellRunToolbar.ts @@ -0,0 +1,140 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; +import { Action, IAction } from 'vs/base/common/actions'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { MarshalledId } from 'vs/base/common/marshalling'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { localize } from 'vs/nls'; +import { DropdownWithPrimaryActionViewItem } from 'vs/platform/actions/browser/dropdownWithPrimaryActionViewItem'; +import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenu, IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; +import { CellViewModelStateChangeEvent, ICellViewModel, INotebookEditorDelegate, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellPart } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellPart'; +import { BaseCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; + +export class RunToolbar extends CellPart { + toolbar!: ToolBar; + + constructor( + readonly notebookEditor: INotebookEditorDelegate, + readonly contextKeyService: IContextKeyService, + readonly cellContainer: HTMLElement, + readonly runButtonContainer: HTMLElement, + @IMenuService readonly menuService: IMenuService, + @IKeybindingService readonly keybindingService: IKeybindingService, + @IContextMenuService readonly contextMenuService: IContextMenuService, + @IInstantiationService readonly instantiationService: IInstantiationService, + ) { + super(); + + const menu = this._register(menuService.createMenu(this.notebookEditor.creationOptions.menuIds.cellExecutePrimary!, contextKeyService)); + this.createRunCellToolbar(runButtonContainer, cellContainer, contextKeyService); + const updateActions = () => { + const actions = this.getCellToolbarActions(menu); + const primary = actions.primary[0]; // Only allow one primary action + this.toolbar.setActions(primary ? [primary] : []); + }; + updateActions(); + this._register(menu.onDidChange(updateActions)); + this._register(this.notebookEditor.notebookOptions.onDidChangeOptions(updateActions)); + } + + renderCell(element: ICellViewModel, templateData: BaseCellRenderTemplate): void { + this.toolbar.context = { + ui: true, + cell: element, + cellTemplate: templateData, + notebookEditor: this.notebookEditor, + $mid: MarshalledId.NotebookCellActionContext + }; + } + + prepareLayout(): void { + // no op + } + + updateLayoutNow(element: ICellViewModel): void { + // no op + } + + updateState(element: ICellViewModel, e: CellViewModelStateChangeEvent): void { + // no op + } + + getCellToolbarActions(menu: IMenu): { primary: IAction[], secondary: IAction[]; } { + const primary: IAction[] = []; + const secondary: IAction[] = []; + const result = { primary, secondary }; + + createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, result, g => /^inline/.test(g)); + + return result; + } + + private createRunCellToolbar(container: HTMLElement, cellContainer: HTMLElement, contextKeyService: IContextKeyService) { + const actionViewItemDisposables = this._register(new DisposableStore()); + const dropdownAction = this._register(new Action('notebook.moreRunActions', localize('notebook.moreRunActionsLabel', "More..."), 'codicon-chevron-down', true)); + + const keybindingProvider = (action: IAction) => this.keybindingService.lookupKeybinding(action.id, executionContextKeyService); + const executionContextKeyService = this._register(getCodeCellExecutionContextKeyService(contextKeyService)); + this.toolbar = this._register(new ToolBar(container, this.contextMenuService, { + getKeyBinding: keybindingProvider, + actionViewItemProvider: _action => { + actionViewItemDisposables.clear(); + + const primaryMenu = actionViewItemDisposables.add(this.menuService.createMenu(this.notebookEditor.creationOptions.menuIds.cellExecutePrimary!, contextKeyService)); + const primary = this.getCellToolbarActions(primaryMenu).primary[0]; + if (!(primary instanceof MenuItemAction)) { + return undefined; + } + + const menu = actionViewItemDisposables.add(this.menuService.createMenu(this.notebookEditor.creationOptions.menuIds.cellExecuteToolbar, contextKeyService)); + const secondary = this.getCellToolbarActions(menu).secondary; + if (!secondary.length) { + return undefined; + } + + const item = this.instantiationService.createInstance(DropdownWithPrimaryActionViewItem, + primary, + dropdownAction, + secondary, + 'notebook-cell-run-toolbar', + this.contextMenuService, + { + getKeyBinding: keybindingProvider + }); + actionViewItemDisposables.add(item.onDidChangeDropdownVisibility(visible => { + cellContainer.classList.toggle('cell-run-toolbar-dropdown-active', visible); + })); + + return item; + }, + renderDropdownAsChildElement: true + })); + } +} + +export function getCodeCellExecutionContextKeyService(contextKeyService: IContextKeyService): IContextKeyService { + // Create a fake ContextKeyService, and look up the keybindings within this context. + const executionContextKeyService = contextKeyService.createScoped(document.createElement('div')); + InputFocusedContext.bindTo(executionContextKeyService).set(true); + EditorContextKeys.editorTextFocus.bindTo(executionContextKeyService).set(true); + EditorContextKeys.focus.bindTo(executionContextKeyService).set(true); + EditorContextKeys.textInputFocus.bindTo(executionContextKeyService).set(true); + NOTEBOOK_CELL_EXECUTION_STATE.bindTo(executionContextKeyService).set('idle'); + NOTEBOOK_CELL_LIST_FOCUSED.bindTo(executionContextKeyService).set(true); + NOTEBOOK_EDITOR_FOCUSED.bindTo(executionContextKeyService).set(true); + NOTEBOOK_CELL_TYPE.bindTo(executionContextKeyService).set('code'); + + return executionContextKeyService; +} diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts similarity index 81% rename from src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts rename to src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts index 86620b5fc48..f1bf7ef082b 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts @@ -25,36 +25,73 @@ import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer'; import { TokenizationRegistry } from 'vs/editor/common/modes'; import { MarkdownCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; import { IModeService } from 'vs/editor/common/services/modeService'; +import { CellEditorOptions } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { CellPart } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellPart'; export class StatefulMarkdownCell extends Disposable { private editor: CodeEditorWidget | null = null; - private markdownAccessibilityContainer: HTMLElement; + private markdownAccessibilityContainer!: HTMLElement; private editorPart: HTMLElement; private readonly localDisposables = this._register(new DisposableStore()); private readonly focusSwitchDisposable = this._register(new MutableDisposable()); private readonly editorDisposables = this._register(new DisposableStore()); private foldingState: CellFoldingState; + private cellEditorOptions: CellEditorOptions; + private editorOptions: IEditorOptions; constructor( private readonly notebookEditor: IActiveNotebookEditorDelegate, private readonly viewCell: MarkupCellViewModel, private readonly templateData: MarkdownCellRenderTemplate, - private editorOptions: IEditorOptions, + private readonly cellParts: CellPart[], private readonly renderedEditors: Map, @IContextKeyService private readonly contextKeyService: IContextKeyService, @INotebookCellStatusBarService readonly notebookCellStatusBarService: INotebookCellStatusBarService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IModeService private readonly modeService: IModeService, + @IConfigurationService private configurationService: IConfigurationService, ) { super(); + this.constructDOM(); + this.editorPart = templateData.editorPart; + + this.cellEditorOptions = this._register(new CellEditorOptions(this.notebookEditor, this.notebookEditor.notebookOptions, this.configurationService, this.viewCell.language)); + this.cellEditorOptions.setLineNumbers(this.viewCell.lineNumbers); + this.editorOptions = this.cellEditorOptions.getValue(this.viewCell.internalMetadata); + + this._register(toDisposable(() => renderedEditors.delete(this.viewCell))); + this.registerListeners(); + + // update for init state + this.cellParts.forEach(cellPart => { + cellPart.renderCell(this.viewCell, this.templateData); + }); + + this.updateForHover(); + this.updateForFocusModeChange(); + this.foldingState = viewCell.foldingState; + this.setFoldingIndicator(); + this.updateFoldingIconShowClass(); + + // the markdown preview's height might already be updated after the renderer calls `element.getHeight()` + if (this.viewCell.layoutInfo.totalHeight > 0) { + this.relayoutCell(); + } + + this.applyDecorations(); + this.viewUpdate(); + } + + private constructDOM() { // Create an element that is only used to announce markup cell content to screen readers const id = `aria-markup-cell-${this.viewCell.id}`; - this.markdownAccessibilityContainer = templateData.cellContainer; + this.markdownAccessibilityContainer = this.templateData.cellContainer; this.markdownAccessibilityContainer.id = id; // Hide the element from non-screen readers this.markdownAccessibilityContainer.style.height = '1px'; @@ -63,48 +100,50 @@ export class StatefulMarkdownCell extends Disposable { this.markdownAccessibilityContainer.ariaHidden = 'false'; this.templateData.rootContainer.setAttribute('aria-describedby', id); - - this.editorPart = templateData.editorPart; - this.templateData.container.classList.toggle('webview-backed-markdown-cell', true); + } - this._register(toDisposable(() => renderedEditors.delete(this.viewCell))); + private registerListeners() { + this._register(this.viewCell.onDidChangeState(e => { + this.cellParts.forEach(cellPart => { + cellPart.updateState(this.viewCell, e); + }); + })); - this._register(viewCell.model.onDidChangeMetadata(() => { + this._register(this.viewCell.model.onDidChangeMetadata(() => { this.viewUpdate(); })); - const updateForFocusMode = () => { - if (viewCell.focusMode === CellFocusMode.Editor) { - this.focusEditorIfNeeded(); - } - - templateData.container.classList.toggle('cell-editor-focus', viewCell.focusMode === CellFocusMode.Editor); - }; - - this._register(viewCell.onDidChangeState((e) => { + this._register(this.viewCell.onDidChangeState((e) => { if (e.editStateChanged || e.contentChanged) { this.viewUpdate(); } if (e.focusModeChanged) { - updateForFocusMode(); + this.updateForFocusModeChange(); } if (e.foldingStateChanged) { - const foldingState = viewCell.foldingState; + const foldingState = this.viewCell.foldingState; if (foldingState !== this.foldingState) { this.foldingState = foldingState; this.setFoldingIndicator(); } } - })); - updateForFocusMode(); - this.foldingState = viewCell.foldingState; - this.setFoldingIndicator(); - this.updateFoldingIconShowClass(); + if (e.cellIsHoveredChanged) { + this.updateForHover(); + } + + if (e.inputCollapsedChanged) { + this.updateCollapsedState(); + } + + if (e.cellLineNumberChanged) { + this.cellEditorOptions.setLineNumbers(this.viewCell.lineNumbers); + } + })); this._register(this.notebookEditor.notebookOptions.onDidChangeOptions(e => { if (e.showFoldingControls) { @@ -112,23 +151,43 @@ export class StatefulMarkdownCell extends Disposable { } })); - this._register(viewCell.onDidChangeLayout((e) => { + this._register(this.viewCell.onDidChangeLayout((e) => { const layoutInfo = this.editor?.getLayoutInfo(); - if (e.outerWidth && this.viewCell.getEditState() === CellEditState.Editing && layoutInfo && layoutInfo.width !== viewCell.layoutInfo.editorWidth) { + if (e.outerWidth && this.viewCell.getEditState() === CellEditState.Editing && layoutInfo && layoutInfo.width !== this.viewCell.layoutInfo.editorWidth) { this.onCellEditorWidthChange(); } else if (e.totalHeight || e.outerWidth) { this.relayoutCell(); } })); - // the markdown preview's height might already be updated after the renderer calls `element.getHeight()` - if (this.viewCell.layoutInfo.totalHeight > 0) { - this.relayoutCell(); + this._register(this.cellEditorOptions.onDidChange(() => { + this.updateEditorOptions(this.cellEditorOptions.getUpdatedValue(this.viewCell.internalMetadata)); + })); + } + + private updateCollapsedState() { + if (this.viewCell.isInputCollapsed) { + this.notebookEditor.hideMarkupPreviews([this.viewCell]); + } else { + this.notebookEditor.unhideMarkupPreviews([this.viewCell]); + } + } + + private updateForHover(): void { + this.templateData.container.classList.toggle('markdown-cell-hover', this.viewCell.cellIsHovered); + } + + private updateForFocusModeChange() { + if (this.viewCell.focusMode === CellFocusMode.Editor) { + this.focusEditorIfNeeded(); } - // apply decorations + this.templateData.container.classList.toggle('cell-editor-focus', this.viewCell.focusMode === CellFocusMode.Editor); + } - this._register(viewCell.onCellDecorationsChanged((e) => { + private applyDecorations() { + // apply decorations + this._register(this.viewCell.onCellDecorationsChanged((e) => { e.added.forEach(options => { if (options.className) { this.notebookEditor.deltaCellOutputContainerClassNames(this.viewCell.id, [options.className], []); @@ -142,16 +201,19 @@ export class StatefulMarkdownCell extends Disposable { }); })); - viewCell.getCellDecorations().forEach(options => { + this.viewCell.getCellDecorations().forEach(options => { if (options.className) { this.notebookEditor.deltaCellOutputContainerClassNames(this.viewCell.id, [options.className], []); } }); - - this.viewUpdate(); } override dispose() { + // move focus back to the cell list otherwise the focus goes to body + if (this.notebookEditor.getActiveCell() === this.viewCell && this.viewCell.focusMode === CellFocusMode.Editor) { + this.notebookEditor.focusContainer(); + } + this.viewCell.detachTextEditor(); super.dispose(); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 953bb39eb64..b371fac872d 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -924,6 +924,10 @@ export class NotebookCellList extends WorkbenchList implements ID super.domFocus(); } + focusContainer() { + super.domFocus(); + } + getViewScrollTop() { return this.view.getScrollTop(); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts index 9ce7173a360..9231bcfc62c 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts @@ -3,25 +3,27 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { FastDomNode } from 'vs/base/browser/fastDomNode'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { IListContextMenuEvent, IListEvent, IListMouseEvent } from 'vs/base/browser/ui/list/list'; import { IListOptions, IListStyles } from 'vs/base/browser/ui/list/listWidget'; -import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; -import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { Event } from 'vs/base/common/event'; -import { FastDomNode } from 'vs/base/browser/fastDomNode'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ScrollEvent } from 'vs/base/common/scrollable'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Range } from 'vs/editor/common/core/range'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ICellOutputViewModel, ICellViewModel, IGenericCellViewModel, INotebookCellOutputLayoutInfo, INotebookEditorCreationOptions, IRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellFocusIndicator } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellFocusIndicator'; +import { CellProgressBar } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellProgressBar'; +import { BetweenCellToolbar, CellTitleToolbarPart } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars'; +import { CellEditorStatusBar } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellWidgets'; +import { RunToolbar } from 'vs/workbench/contrib/notebook/browser/view/cellParts/codeCellRunToolbar'; import { CellViewModel, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { IOutputItemDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; -import { IMenu } from 'vs/platform/actions/common/actions'; -import { CellEditorStatusBar } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets'; -import { ICellOutputViewModel, ICellViewModel, IGenericCellViewModel, INotebookCellOutputLayoutInfo, INotebookEditorCreationOptions, IRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; export interface INotebookCellList { isDisposed: boolean; @@ -80,6 +82,7 @@ export interface INotebookCellList { triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent): void; updateElementHeight2(element: ICellViewModel, size: number): void; domFocus(): void; + focusContainer(): void; setCellSelection(element: ICellViewModel, range: Range): void; style(styles: IListStyles): void; getRenderHeight(): number; @@ -92,44 +95,35 @@ export interface BaseCellRenderTemplate { rootContainer: HTMLElement; editorPart: HTMLElement; cellInputCollapsedContainer: HTMLElement; - contextKeyService: IContextKeyService; + instantiationService: IInstantiationService; container: HTMLElement; cellContainer: HTMLElement; decorationContainer: HTMLElement; - toolbar: ToolBar; - deleteToolbar: ToolBar; - betweenCellToolbar: ToolBar; - focusIndicatorLeft: FastDomNode; - focusIndicatorRight: FastDomNode; - readonly disposables: DisposableStore; + betweenCellToolbar: BetweenCellToolbar; + titleToolbar: CellTitleToolbarPart; + focusIndicator: CellFocusIndicator; + readonly templateDisposables: DisposableStore; readonly elementDisposables: DisposableStore; - bottomCellContainer: HTMLElement; currentRenderedCell?: ICellViewModel; statusBar: CellEditorStatusBar; - titleMenu: IMenu; toJSON: () => object; } export interface MarkdownCellRenderTemplate extends BaseCellRenderTemplate { editorContainer: HTMLElement; foldingIndicator: HTMLElement; - focusIndicatorBottom: HTMLElement; currentEditor?: ICodeEditor; } export interface CodeCellRenderTemplate extends BaseCellRenderTemplate { - runToolbar: ToolBar; - runButtonContainer: HTMLElement; + runToolbar: RunToolbar; executionOrderLabel: HTMLElement; outputContainer: FastDomNode; cellOutputCollapsedContainer: HTMLElement; outputShowMoreContainer: FastDomNode; focusSinkElement: HTMLElement; editor: ICodeEditor; - progressBar: ProgressBar; - collapsedProgressBar: ProgressBar; - focusIndicatorRight: FastDomNode; - focusIndicatorBottom: FastDomNode; + progressBar: CellProgressBar; dragHandle: FastDomNode; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts index 57f448967a6..73e7e871db0 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts @@ -75,7 +75,7 @@ class StreamRendererContrib extends Disposable implements IOutputRendererContrib const text = getStringValue(item); const contentNode = DOM.$('span.output-stream'); const lineLimit = this.configurationService.getValue(NotebookSetting.textOutputLineLimit) ?? 30; - truncatedArrayOfString(notebookUri, output.cellViewModel, Math.max(lineLimit, 6), contentNode, [text], disposables, linkDetector, this.openerService, this.themeService); + truncatedArrayOfString(notebookUri, output.cellViewModel, output.model.outputId, Math.max(lineLimit, 6), contentNode, [text], disposables, linkDetector, this.openerService, this.themeService); container.appendChild(contentNode); return { type: RenderOutputType.Mainframe, disposable: disposables }; @@ -179,7 +179,7 @@ class PlainTextRendererContrib extends Disposable implements IOutputRendererCont const str = getStringValue(item); const contentNode = DOM.$('.output-plaintext'); const lineLimit = this.configurationService.getValue(NotebookSetting.textOutputLineLimit) ?? 30; - truncatedArrayOfString(notebookUri, output.cellViewModel, Math.max(lineLimit, 6), contentNode, [str], disposables, linkDetector, this.openerService, this.themeService); + truncatedArrayOfString(notebookUri, output.cellViewModel, output.model.outputId, Math.max(lineLimit, 6), contentNode, [str], disposables, linkDetector, this.openerService, this.themeService); container.appendChild(contentNode); return { type: RenderOutputType.Mainframe, supportAppend: true, disposable: disposables }; diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/textHelper.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/textHelper.ts index a30a41e387b..54cbf6b13ed 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/textHelper.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/textHelper.ts @@ -7,7 +7,6 @@ import * as DOM from 'vs/base/browser/dom'; import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { Range } from 'vs/editor/common/core/range'; import { DefaultEndOfLine, EndOfLinePreference, ITextBuffer } from 'vs/editor/common/model'; @@ -21,9 +20,9 @@ import { CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; const SIZE_LIMIT = 65535; -function generateViewMoreElement(notebookUri: URI, cellViewModel: IGenericCellViewModel, disposables: DisposableStore, openerService: IOpenerService): HTMLElement { +function generateViewMoreElement(notebookUri: URI, cellViewModel: IGenericCellViewModel, outputId: string, disposables: DisposableStore, openerService: IOpenerService): HTMLElement { const md: IMarkdownString = { - value: '[show more (open the raw output data in a text editor) ...](command:workbench.action.openLargeOutput)', + value: `[show more (open the raw output data in a text editor) ...](command:workbench.action.openLargeOutput?${outputId})`, isTrusted: true, supportThemeIcons: true }; @@ -31,8 +30,10 @@ function generateViewMoreElement(notebookUri: URI, cellViewModel: IGenericCellVi const rendered = disposables.add(renderMarkdown(md, { actionHandler: { callback: (content) => { - if (content === 'command:workbench.action.openLargeOutput') { - openerService.open(CellUri.generateCellUri(notebookUri, cellViewModel.handle, Schemas.vscodeNotebookCellOutput)); + const ret = /command\:workbench\.action\.openLargeOutput\?(.*)/.exec(content); + if (ret && ret.length === 2) { + const outputId = ret[1]; + openerService.open(CellUri.generateCellOutputUri(notebookUri, cellViewModel.handle, outputId)); } return; @@ -45,7 +46,7 @@ function generateViewMoreElement(notebookUri: URI, cellViewModel: IGenericCellVi return rendered.element; } -export function truncatedArrayOfString(notebookUri: URI, cellViewModel: IGenericCellViewModel, linesLimit: number, container: HTMLElement, outputs: string[], disposables: DisposableStore, linkDetector: LinkDetector, openerService: IOpenerService, themeService: IThemeService) { +export function truncatedArrayOfString(notebookUri: URI, cellViewModel: IGenericCellViewModel, outputId: string, linesLimit: number, container: HTMLElement, outputs: string[], disposables: DisposableStore, linkDetector: LinkDetector, openerService: IOpenerService, themeService: IThemeService) { const fullLen = outputs.reduce((p, c) => { return p + c.length; }, 0); @@ -63,7 +64,7 @@ export function truncatedArrayOfString(notebookUri: URI, cellViewModel: IGeneric const truncatedText = buffer.getValueInRange(new Range(1, 1, sizeBufferLimitPosition.lineNumber, sizeBufferLimitPosition.column), EndOfLinePreference.TextDefined); container.appendChild(handleANSIOutput(truncatedText, linkDetector, themeService, undefined)); // view more ... - container.appendChild(generateViewMoreElement(notebookUri, cellViewModel, disposables, openerService)); + container.appendChild(generateViewMoreElement(notebookUri, cellViewModel, outputId, disposables, openerService)); return; } } @@ -87,7 +88,7 @@ export function truncatedArrayOfString(notebookUri: URI, cellViewModel: IGeneric pre.appendChild(handleANSIOutput(buffer.getValueInRange(new Range(1, 1, linesLimit - 5, buffer.getLineLastNonWhitespaceColumn(linesLimit - 5)), EndOfLinePreference.TextDefined), linkDetector, themeService, undefined)); // view more ... - container.appendChild(generateViewMoreElement(notebookUri, cellViewModel, disposables, openerService)); + container.appendChild(generateViewMoreElement(notebookUri, cellViewModel, outputId, disposables, openerService)); const lineCount = buffer.getLineCount(); const pre2 = DOM.$('div'); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index c50030657da..59781739613 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -27,7 +27,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IFileService } from 'vs/platform/files/common/files'; -import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener'; +import { IOpenerService, matchesScheme, matchesSomeScheme } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; @@ -292,6 +292,7 @@ export class BackLayerWebView extends Disposable { #container > div.preview.dragging { background-color: var(--theme-background); + opacity: 0.5 !important; } .monaco-workbench.vs-dark .notebookOverlay .cell.markdown .latex img, @@ -615,6 +616,16 @@ var requirejs = (function() { this._onDidClickDataLink(data); break; } + case 'clicked-link': + { + if (matchesSomeScheme(data.href, Schemas.http, Schemas.https, Schemas.mailto)) { + this.openerService.open(data.href, { fromUserGesture: true }); + } else if (!/^[\w\-]+:/.test(data.href)) { + const path = URI.joinPath(dirname(this.documentUri), data.href); + this.openerService.open(path, { fromUserGesture: true }); + } + break; + } case 'customKernelMessage': { this._onMessage.fire({ message: data.message }); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index 85f42fee93b..4330548950d 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -7,13 +7,9 @@ import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser'; import * as DOM from 'vs/base/browser/dom'; import { FastDomNode } from 'vs/base/browser/fastDomNode'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; -import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; -import { Action, IAction } from 'vs/base/common/actions'; import { Codicon, CSSIcon } from 'vs/base/common/codicons'; import { Color } from 'vs/base/common/color'; import { combinedDisposable, Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { MarshalledId } from 'vs/base/common/marshalling'; import * as platform from 'vs/base/common/platform'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; @@ -25,33 +21,31 @@ import { ITextModel } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer'; import { localize } from 'vs/nls'; -import { DropdownWithPrimaryActionViewItem } from 'vs/platform/actions/browser/dropdownWithPrimaryActionViewItem'; -import { createActionViewItem, createAndFillInActionBarActions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IMenu, IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { IMenuService } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { INotebookActionContext, INotebookCellActionContext, INotebookCellToolbarActionContext } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; -import { DeleteCellAction } from 'vs/workbench/contrib/notebook/browser/controller/editActions'; -import { CodeCellLayoutInfo, EXPAND_CELL_OUTPUT_COMMAND_ID, ICellViewModel, INotebookEditorDelegate, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { BaseCellRenderTemplate, CodeCellRenderTemplate, isCodeCellRenderTemplate, MarkdownCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; -import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellActionView'; -import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys'; -import { CellDragAndDropController, DRAGGING_CLASS } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellDnd'; -import { CellEditorOptions } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellEditorOptions'; -import { CellEditorStatusBar } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets'; -import { CodeCell } from 'vs/workbench/contrib/notebook/browser/view/renderers/codeCell'; -import { StatefulMarkdownCell } from 'vs/workbench/contrib/notebook/browser/view/renderers/markdownCell'; +import { CodeCellLayoutInfo, EXPAND_CELL_OUTPUT_COMMAND_ID, ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys'; +import { CellDecorations } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellDecorations'; +import { CellDragAndDropController } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd'; +import { CellEditorOptions } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions'; +import { CellFocusIndicator } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellFocusIndicator'; +import { CellProgressBar } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellProgressBar'; +import { BetweenCellToolbar, CellTitleToolbarPart } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars'; +import { CellEditorStatusBar } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellWidgets'; +import { CodeCell } from 'vs/workbench/contrib/notebook/browser/view/cellParts/codeCell'; +import { RunToolbar } from 'vs/workbench/contrib/notebook/browser/view/cellParts/codeCellRunToolbar'; +import { StatefulMarkdownCell } from 'vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell'; +import { BaseCellRenderTemplate, CodeCellRenderTemplate, MarkdownCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { CellKind, NotebookCellExecutionState, NotebookCellInternalMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions'; +import { CellKind, NotebookCellInternalMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; const $ = DOM.$; @@ -111,139 +105,8 @@ abstract class AbstractCellRenderer { this.dndController = undefined; } - protected createBetweenCellToolbar(container: HTMLElement, disposables: DisposableStore, contextKeyService: IContextKeyService, notebookOptions: NotebookOptions): ToolBar { - const toolbar = new ToolBar(container, this.contextMenuService, { - actionViewItemProvider: action => { - if (action instanceof MenuItemAction) { - if (notebookOptions.getLayoutConfiguration().insertToolbarAlignment === 'center') { - return this.instantiationService.createInstance(CodiconActionViewItem, action); - } else { - return this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined); - } - } - - return undefined; - } - }); - disposables.add(toolbar); - - const menu = disposables.add(this.menuService.createMenu(this.notebookEditor.creationOptions.menuIds.cellInsertToolbar, contextKeyService)); - const updateActions = () => { - const actions = this.getCellToolbarActions(menu); - toolbar.setActions(actions.primary, actions.secondary); - }; - - disposables.add(menu.onDidChange(() => updateActions())); - disposables.add(notebookOptions.onDidChangeOptions((e) => { - if (e.insertToolbarAlignment) { - updateActions(); - } - })); - updateActions(); - - return toolbar; - } - - protected setBetweenCellToolbarContext(templateData: BaseCellRenderTemplate, element: CodeCellViewModel | MarkupCellViewModel, context: INotebookCellActionContext): void { - templateData.betweenCellToolbar.context = context; - - const container = templateData.bottomCellContainer; - const bottomToolbarOffset = element.layoutInfo.bottomToolbarOffset; - container.style.transform = `translateY(${bottomToolbarOffset}px)`; - - templateData.elementDisposables.add(element.onDidChangeLayout(() => { - const bottomToolbarOffset = element.layoutInfo.bottomToolbarOffset; - container.style.transform = `translateY(${bottomToolbarOffset}px)`; - })); - } - - protected createToolbar(container: HTMLElement, elementClass?: string): ToolBar { - const toolbar = new ToolBar(container, this.contextMenuService, { - getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), - actionViewItemProvider: action => { - return createActionViewItem(this.instantiationService, action); - }, - renderDropdownAsChildElement: true - }); - - if (elementClass) { - toolbar.getElement().classList.add(elementClass); - } - - return toolbar; - } - - protected getCellToolbarActions(menu: IMenu): { primary: IAction[], secondary: IAction[]; } { - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; - - createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, result, g => /^inline/.test(g)); - - return result; - } - - protected setupCellToolbarActions(templateData: BaseCellRenderTemplate, disposables: DisposableStore): void { - const updateActions = () => { - const actions = this.getCellToolbarActions(templateData.titleMenu); - - const hadFocus = DOM.isAncestor(document.activeElement, templateData.toolbar.getElement()); - templateData.toolbar.setActions(actions.primary, actions.secondary); - if (hadFocus) { - this.notebookEditor.focus(); - } - - const layoutInfo = this.notebookEditor.notebookOptions.getLayoutConfiguration(); - if (actions.primary.length || actions.secondary.length) { - templateData.container.classList.add('cell-has-toolbar-actions'); - if (isCodeCellRenderTemplate(templateData)) { - templateData.focusIndicatorLeft.domNode.style.transform = `translateY(${layoutInfo.editorToolbarHeight + layoutInfo.cellTopMargin}px)`; - templateData.focusIndicatorRight.domNode.style.transform = `translateY(${layoutInfo.editorToolbarHeight + layoutInfo.cellTopMargin}px)`; - } - } else { - templateData.container.classList.remove('cell-has-toolbar-actions'); - if (isCodeCellRenderTemplate(templateData)) { - templateData.focusIndicatorLeft.domNode.style.transform = `translateY(${layoutInfo.cellTopMargin}px)`; - templateData.focusIndicatorRight.domNode.style.transform = `translateY(${layoutInfo.cellTopMargin}px)`; - } - } - }; - - // #103926 - let dropdownIsVisible = false; - let deferredUpdate: (() => void) | undefined; - - updateActions(); - disposables.add(templateData.titleMenu.onDidChange(() => { - if (this.notebookEditor.isDisposed) { - return; - } - - if (dropdownIsVisible) { - deferredUpdate = () => updateActions(); - return; - } - - updateActions(); - })); - templateData.container.classList.toggle('cell-toolbar-dropdown-active', false); - disposables.add(templateData.toolbar.onDidChangeDropdownVisibility(visible => { - dropdownIsVisible = visible; - templateData.container.classList.toggle('cell-toolbar-dropdown-active', visible); - - if (deferredUpdate && !visible) { - setTimeout(() => { - if (deferredUpdate) { - deferredUpdate(); - } - }, 0); - deferredUpdate = undefined; - } - })); - } - protected commonRenderTemplate(templateData: BaseCellRenderTemplate): void { - templateData.disposables.add(DOM.addDisposableListener(templateData.container, DOM.EventType.FOCUS, () => { + templateData.templateDisposables.add(DOM.addDisposableListener(templateData.container, DOM.EventType.FOCUS, () => { if (templateData.currentRenderedCell) { this.notebookEditor.focusElement(templateData.currentRenderedCell); } @@ -251,11 +114,9 @@ abstract class AbstractCellRenderer { } protected commonRenderElement(element: ICellViewModel, templateData: BaseCellRenderTemplate): void { - if (element.dragging) { - templateData.container.classList.add(DRAGGING_CLASS); - } else { - templateData.container.classList.remove(DRAGGING_CLASS); - } + this.dndController?.renderElement(element, templateData); + templateData.elementDisposables.add(new CellDecorations(templateData.rootContainer, templateData.decorationContainer, element)); + templateData.elementDisposables.add(templateData.instantiationService.createInstance(CellContextKeyManager, this.notebookEditor, element)); } } @@ -267,7 +128,7 @@ export class MarkupCellRenderer extends AbstractCellRenderer implements IListRen dndController: CellDragAndDropController, private renderedEditors: Map, contextKeyServiceProvider: (container: HTMLElement) => IContextKeyService, - @IConfigurationService private configurationService: IConfigurationService, + @IConfigurationService configurationService: IConfigurationService, @IInstantiationService instantiationService: IInstantiationService, @IContextMenuService contextMenuService: IContextMenuService, @IMenuService menuService: IMenuService, @@ -284,18 +145,14 @@ export class MarkupCellRenderer extends AbstractCellRenderer implements IListRen renderTemplate(rootContainer: HTMLElement): MarkdownCellRenderTemplate { rootContainer.classList.add('markdown-cell-row'); const container = DOM.append(rootContainer, DOM.$('.cell-inner-container')); - const disposables = new DisposableStore(); - const contextKeyService = disposables.add(this.contextKeyServiceProvider(container)); + const templateDisposables = new DisposableStore(); + const contextKeyService = templateDisposables.add(this.contextKeyServiceProvider(container)); const decorationContainer = DOM.append(rootContainer, $('.cell-decoration')); const titleToolbarContainer = DOM.append(container, $('.cell-title-toolbar')); - const toolbar = disposables.add(this.createToolbar(titleToolbarContainer)); - const deleteToolbar = disposables.add(this.createToolbar(titleToolbarContainer, 'cell-delete-toolbar')); - if (!this.notebookEditor.creationOptions.isReadOnly) { - deleteToolbar.setActions([this.instantiationService.createInstance(DeleteCellAction)]); - } - DOM.append(container, $('.cell-focus-indicator.cell-focus-indicator-top')); + const focusIndicatorTop = new FastDomNode(DOM.append(container, $('.cell-focus-indicator.cell-focus-indicator-top'))); const focusIndicatorLeft = new FastDomNode(DOM.append(container, DOM.$('.cell-focus-indicator.cell-focus-indicator-side.cell-focus-indicator-left'))); + const foldingIndicator = DOM.append(focusIndicatorLeft.domNode, DOM.$('.notebook-folding-indicator')); const focusIndicatorRight = new FastDomNode(DOM.append(container, DOM.$('.cell-focus-indicator.cell-focus-indicator-side.cell-focus-indicator-right'))); const codeInnerContent = DOM.append(container, $('.cell.code')); @@ -305,36 +162,37 @@ export class MarkupCellRenderer extends AbstractCellRenderer implements IListRen editorPart.style.display = 'none'; const innerContent = DOM.append(container, $('.cell.markdown')); - const foldingIndicator = DOM.append(focusIndicatorLeft.domNode, DOM.$('.notebook-folding-indicator')); - const bottomCellContainer = DOM.append(container, $('.cell-bottom-toolbar-container')); - const betweenCellToolbar = disposables.add(this.createBetweenCellToolbar(bottomCellContainer, disposables, contextKeyService, this.notebookEditor.notebookOptions)); - const focusIndicatorBottom = DOM.append(container, $('.cell-focus-indicator.cell-focus-indicator-bottom')); - const statusBar = disposables.add(this.instantiationService.createInstance(CellEditorStatusBar, editorPart)); - - const titleMenu = disposables.add(this.menuService.createMenu(this.notebookEditor.creationOptions.menuIds.cellTitleToolbar, contextKeyService)); + const scopedInstaService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyService])); + const rootClassDelegate = { + toggle: (className: string, force?: boolean) => container.classList.toggle(className, force) + }; + const titleToolbar = templateDisposables.add(scopedInstaService.createInstance( + CellTitleToolbarPart, + titleToolbarContainer, + rootClassDelegate, + this.notebookEditor.creationOptions.menuIds.cellTitleToolbar, + this.notebookEditor)); + const betweenCellToolbar = templateDisposables.add(scopedInstaService.createInstance(BetweenCellToolbar, this.notebookEditor, titleToolbarContainer, bottomCellContainer)); + const focusIndicatorBottom = new FastDomNode(DOM.append(container, $('.cell-focus-indicator.cell-focus-indicator-bottom'))); + const statusBar = templateDisposables.add(this.instantiationService.createInstance(CellEditorStatusBar, this.notebookEditor, editorPart)); const templateData: MarkdownCellRenderTemplate = { rootContainer, cellInputCollapsedContainer, - contextKeyService, + instantiationService: scopedInstaService, container, decorationContainer, cellContainer: innerContent, editorPart, editorContainer, - focusIndicatorLeft, - focusIndicatorBottom, - focusIndicatorRight, + focusIndicator: new CellFocusIndicator(this.notebookEditor, focusIndicatorTop, focusIndicatorLeft, focusIndicatorRight, focusIndicatorBottom), foldingIndicator, - disposables, + templateDisposables, elementDisposables: new DisposableStore(), - toolbar, - deleteToolbar, betweenCellToolbar, - bottomCellContainer, - titleMenu, + titleToolbar, statusBar, toJSON: () => { return {}; } }; @@ -349,19 +207,6 @@ export class MarkupCellRenderer extends AbstractCellRenderer implements IListRen throw new Error('The notebook editor is not attached with view model yet.'); } - const removedClassNames: string[] = []; - templateData.rootContainer.classList.forEach(className => { - if (/^nb\-.*$/.test(className)) { - removedClassNames.push(className); - } - }); - - removedClassNames.forEach(className => { - templateData.rootContainer.classList.remove(className); - }); - - templateData.decorationContainer.innerText = ''; - this.commonRenderElement(element, templateData); templateData.currentRenderedCell = element; @@ -375,94 +220,25 @@ export class MarkupCellRenderer extends AbstractCellRenderer implements IListRen const elementDisposables = templateData.elementDisposables; - const generateCellTopDecorations = () => { - templateData.decorationContainer.innerText = ''; - - element.getCellDecorations().filter(options => options.topClassName !== undefined).forEach(options => { - templateData.decorationContainer.append(DOM.$(`.${options.topClassName!}`)); - }); - }; - - elementDisposables.add(element.onCellDecorationsChanged((e) => { - const modified = e.added.find(e => e.topClassName) || e.removed.find(e => e.topClassName); - - if (modified) { - generateCellTopDecorations(); - } - })); - - elementDisposables.add(new CellContextKeyManager(templateData.contextKeyService, this.notebookEditor, element)); - this.updateForLayout(element, templateData); elementDisposables.add(element.onDidChangeLayout(() => { this.updateForLayout(element, templateData); })); - this.updateForHover(element, templateData); - const cellEditorOptions = new CellEditorOptions(this.notebookEditor, this.notebookEditor.notebookOptions, this.configurationService, element.language); - cellEditorOptions.setLineNumbers(element.lineNumbers); - elementDisposables.add(cellEditorOptions); - - elementDisposables.add(element.onDidChangeState(e => { - if (e.cellIsHoveredChanged) { - this.updateForHover(element, templateData); - } - - if (e.inputCollapsedChanged) { - this.updateCollapsedState(element); - } - - if (e.cellLineNumberChanged) { - cellEditorOptions.setLineNumbers(element.lineNumbers); - } - })); - - // render toolbar first - this.setupCellToolbarActions(templateData, elementDisposables); - - const toolbarContext = { - ui: true, - cell: element, - notebookEditor: this.notebookEditor, - $mid: MarshalledId.NotebookCellActionContext - }; - templateData.toolbar.context = toolbarContext; - templateData.deleteToolbar.context = toolbarContext; - - this.setBetweenCellToolbarContext(templateData, element, toolbarContext); - - const scopedInstaService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, templateData.contextKeyService])); - const markdownCell = scopedInstaService.createInstance(StatefulMarkdownCell, this.notebookEditor, element, templateData, cellEditorOptions.getValue(element.internalMetadata), this.renderedEditors,); - elementDisposables.add(markdownCell); - elementDisposables.add(cellEditorOptions.onDidChange(newValue => markdownCell.updateEditorOptions(cellEditorOptions.getUpdatedValue(element.internalMetadata)))); - - templateData.statusBar.update(toolbarContext); + elementDisposables.add(templateData.instantiationService.createInstance(StatefulMarkdownCell, this.notebookEditor, element, templateData, [ + templateData.betweenCellToolbar, + templateData.statusBar + ], this.renderedEditors)); } private updateForLayout(element: MarkupCellViewModel, templateData: MarkdownCellRenderTemplate): void { - const indicatorPostion = this.notebookEditor.notebookOptions.computeIndicatorPosition(element.layoutInfo.totalHeight, this.notebookEditor.textModel?.viewType); - templateData.focusIndicatorBottom.style.transform = `translateY(${indicatorPostion.bottomIndicatorTop}px)`; - - templateData.focusIndicatorLeft.setHeight(indicatorPostion.verticalIndicatorHeight); - templateData.focusIndicatorRight.setHeight(indicatorPostion.verticalIndicatorHeight); - + templateData.focusIndicator.updateLayoutNow(element); templateData.container.classList.toggle('cell-statusbar-hidden', this.notebookEditor.notebookOptions.computeEditorStatusbarHeight(element.internalMetadata) === 0); - } - - private updateForHover(element: MarkupCellViewModel, templateData: MarkdownCellRenderTemplate): void { - templateData.container.classList.toggle('markdown-cell-hover', element.cellIsHovered); - } - - private updateCollapsedState(element: MarkupCellViewModel) { - if (element.isInputCollapsed) { - this.notebookEditor.hideMarkupPreviews([element]); - } else { - this.notebookEditor.unhideMarkupPreviews([element]); - } + templateData.betweenCellToolbar.updateLayoutNow(element); } disposeTemplate(templateData: MarkdownCellRenderTemplate): void { - templateData.disposables.clear(); + templateData.templateDisposables.clear(); } disposeElement(element: ICellViewModel, _index: number, templateData: MarkdownCellRenderTemplate): void { @@ -610,24 +386,19 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende renderTemplate(rootContainer: HTMLElement): CodeCellRenderTemplate { rootContainer.classList.add('code-cell-row'); const container = DOM.append(rootContainer, DOM.$('.cell-inner-container')); - const disposables = new DisposableStore(); - const contextKeyService = disposables.add(this.contextKeyServiceProvider(container)); + const templateDisposables = new DisposableStore(); + const contextKeyService = templateDisposables.add(this.contextKeyServiceProvider(container)); const decorationContainer = DOM.append(rootContainer, $('.cell-decoration')); - DOM.append(container, $('.cell-focus-indicator.cell-focus-indicator-top')); + const focusIndicatorTop = new FastDomNode(DOM.append(container, $('.cell-focus-indicator.cell-focus-indicator-top'))); const titleToolbarContainer = DOM.append(container, $('.cell-title-toolbar')); - const toolbar = disposables.add(this.createToolbar(titleToolbarContainer)); - const deleteToolbar = disposables.add(this.createToolbar(titleToolbarContainer, 'cell-delete-toolbar')); - if (!this.notebookEditor.creationOptions.isReadOnly) { - deleteToolbar.setActions([this.instantiationService.createInstance(DeleteCellAction)]); - } - const focusIndicator = new FastDomNode(DOM.append(container, DOM.$('.cell-focus-indicator.cell-focus-indicator-side.cell-focus-indicator-left'))); + const focusIndicatorLeft = new FastDomNode(DOM.append(container, DOM.$('.cell-focus-indicator.cell-focus-indicator-side.cell-focus-indicator-left'))); const dragHandle = new FastDomNode(DOM.append(container, DOM.$('.cell-drag-handle'))); const cellContainer = DOM.append(container, $('.cell.code')); const runButtonContainer = DOM.append(cellContainer, $('.run-button-container')); const cellInputCollapsedContainer = DOM.append(cellContainer, $('.input-collapse-container')); - const runToolbar = this.setupRunToolbar(runButtonContainer, container, contextKeyService, disposables); + const runToolbar = templateDisposables.add(this.instantiationService.createInstance(RunToolbar, this.notebookEditor, contextKeyService, container, runButtonContainer)); const executionOrderLabel = DOM.append(cellContainer, $('div.execution-count-label')); executionOrderLabel.title = localize('cellExecutionOrderCountLabel', 'Execution Order'); @@ -635,7 +406,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende const editorContainer = DOM.append(editorPart, $('.cell-editor-container')); // create a special context key service that set the inCompositeEditor-contextkey - const editorContextKeyService = disposables.add(this.contextKeyServiceProvider(editorPart)); + const editorContextKeyService = templateDisposables.add(this.contextKeyServiceProvider(editorPart)); const editorInstaService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, editorContextKeyService])); EditorContextKeys.inCompositeEditor.bindTo(editorContextKeyService).set(true); @@ -650,17 +421,11 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende contributions: this.notebookEditor.creationOptions.cellEditorContributions }); - disposables.add(editor); + templateDisposables.add(editor); - const progressBar = new ProgressBar(editorPart); - progressBar.hide(); - disposables.add(progressBar); + const progressBar = templateDisposables.add(new CellProgressBar(editorPart, cellInputCollapsedContainer)); - const collapsedProgressBar = new ProgressBar(cellInputCollapsedContainer); - collapsedProgressBar.hide(); - disposables.add(collapsedProgressBar); - - const statusBar = disposables.add(this.instantiationService.createInstance(CellEditorStatusBar, editorPart)); + const statusBar = templateDisposables.add(this.instantiationService.createInstance(CellEditorStatusBar, this.notebookEditor, editorPart)); const outputContainer = new FastDomNode(DOM.append(container, $('.output'))); const cellOutputCollapsedContainer = DOM.append(outputContainer.domNode, $('.output-collapse-container')); @@ -670,55 +435,59 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende const focusSinkElement = DOM.append(container, $('.cell-editor-focus-sink')); focusSinkElement.setAttribute('tabindex', '0'); - const bottomCellContainer = DOM.append(container, $('.cell-bottom-toolbar-container')); + const bottomCellToolbarContainer = DOM.append(container, $('.cell-bottom-toolbar-container')); const focusIndicatorBottom = new FastDomNode(DOM.append(container, $('.cell-focus-indicator.cell-focus-indicator-bottom'))); - const betweenCellToolbar = this.createBetweenCellToolbar(bottomCellContainer, disposables, contextKeyService, this.notebookEditor.notebookOptions); - const titleMenu = disposables.add(this.menuService.createMenu(this.notebookEditor.creationOptions.menuIds.cellTitleToolbar, contextKeyService)); + const scopedInstaService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyService])); + const rootClassDelegate = { + toggle: (className: string, force?: boolean) => container.classList.toggle(className, force) + }; + const titleToolbar = templateDisposables.add(scopedInstaService.createInstance( + CellTitleToolbarPart, + titleToolbarContainer, + rootClassDelegate, + this.notebookEditor.creationOptions.menuIds.cellTitleToolbar, + this.notebookEditor)); + const betweenCellToolbar = templateDisposables.add(scopedInstaService.createInstance(BetweenCellToolbar, this.notebookEditor, titleToolbarContainer, bottomCellToolbarContainer)); const templateData: CodeCellRenderTemplate = { rootContainer, editorPart, cellInputCollapsedContainer, cellOutputCollapsedContainer, - contextKeyService, + instantiationService: scopedInstaService, container, decorationContainer, cellContainer, progressBar, - collapsedProgressBar, statusBar, - focusIndicatorLeft: focusIndicator, - focusIndicatorRight, - focusIndicatorBottom, - toolbar, - deleteToolbar, + focusIndicator: new CellFocusIndicator(this.notebookEditor, focusIndicatorTop, focusIndicatorLeft, focusIndicatorRight, focusIndicatorBottom), + titleToolbar, betweenCellToolbar, focusSinkElement, runToolbar, - runButtonContainer, executionOrderLabel, outputContainer, outputShowMoreContainer, editor, - disposables, + templateDisposables, elementDisposables: new DisposableStore(), - bottomCellContainer, - titleMenu, dragHandle, toJSON: () => { return {}; } }; + this.setupOutputCollapsedPart(templateData); + this.dndController?.registerDragHandle(templateData, rootContainer, dragHandle.domNode, () => new CodeCellDragImageRenderer().getDragImage(templateData, templateData.editor, 'code')); - disposables.add(this.addCollapseClickCollapseHandler(templateData)); - disposables.add(DOM.addDisposableListener(focusSinkElement, DOM.EventType.FOCUS, () => { + templateDisposables.add(this.addCollapseClickCollapseHandler(templateData)); + templateDisposables.add(DOM.addDisposableListener(focusSinkElement, DOM.EventType.FOCUS, () => { if (templateData.currentRenderedCell && (templateData.currentRenderedCell as CodeCellViewModel).outputsViewModels.length) { this.notebookEditor.focusNotebookCell(templateData.currentRenderedCell, 'output'); } })); - disposables.add(this.notebookEditor.onDidChangeActiveKernel(() => { + templateDisposables.add(this.notebookEditor.onDidChangeActiveKernel(() => { if (templateData.currentRenderedCell) { this.updateForKernel(templateData.currentRenderedCell as CodeCellViewModel, templateData); } @@ -729,9 +498,10 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende return templateData; } - private setupOutputCollapsedPart(templateData: CodeCellRenderTemplate, cellOutputCollapseContainer: HTMLElement, element: CodeCellViewModel) { + private setupOutputCollapsedPart(templateData: CodeCellRenderTemplate) { + const cellOutputCollapseContainer = templateData.cellOutputCollapsedContainer; const placeholder = DOM.append(cellOutputCollapseContainer, $('span.expandOutputPlaceholder')) as HTMLElement; - placeholder.textContent = 'Outputs are collapsed'; + placeholder.textContent = localize('cellOutputsCollapsedMsg', "Outputs are collapsed"); const expandIcon = DOM.append(cellOutputCollapseContainer, $('span.expandOutputIcon')); expandIcon.classList.add(...CSSIcon.asClassNameArray(Codicon.more)); @@ -758,11 +528,11 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende templateData.currentRenderedCell.isOutputCollapsed = !templateData.currentRenderedCell.isOutputCollapsed; }; - templateData.disposables.add(DOM.addDisposableListener(expandIcon, DOM.EventType.CLICK, () => { + templateData.templateDisposables.add(DOM.addDisposableListener(expandIcon, DOM.EventType.CLICK, () => { expand(); })); - templateData.disposables.add(DOM.addDisposableListener(cellOutputCollapseContainer, DOM.EventType.DBLCLICK, () => { + templateData.templateDisposables.add(DOM.addDisposableListener(cellOutputCollapseContainer, DOM.EventType.DBLCLICK, () => { expand(); })); } @@ -812,72 +582,6 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende return combinedDisposable(dragHandleListener, collapsedPartListener, clickHandler); } - private createRunCellToolbar(container: HTMLElement, cellContainer: HTMLElement, contextKeyService: IContextKeyService, disposables: DisposableStore): ToolBar { - const actionViewItemDisposables = disposables.add(new DisposableStore()); - const dropdownAction = disposables.add(new Action('notebook.moreRunActions', localize('notebook.moreRunActionsLabel', "More..."), 'codicon-chevron-down', true)); - - const keybindingProvider = (action: IAction) => this.keybindingService.lookupKeybinding(action.id, executionContextKeyService); - const executionContextKeyService = disposables.add(getCodeCellExecutionContextKeyService(contextKeyService)); - const toolbar = disposables.add(new ToolBar(container, this.contextMenuService, { - getKeyBinding: keybindingProvider, - actionViewItemProvider: _action => { - actionViewItemDisposables.clear(); - - const primaryMenu = actionViewItemDisposables.add(this.menuService.createMenu(this.notebookEditor.creationOptions.menuIds.cellExecutePrimary!, contextKeyService)); - const primary = this.getCellToolbarActions(primaryMenu).primary[0]; - if (!(primary instanceof MenuItemAction)) { - return undefined; - } - - const menu = actionViewItemDisposables.add(this.menuService.createMenu(this.notebookEditor.creationOptions.menuIds.cellExecuteToolbar, contextKeyService)); - const secondary = this.getCellToolbarActions(menu).secondary; - if (!secondary.length) { - return undefined; - } - - const item = this.instantiationService.createInstance(DropdownWithPrimaryActionViewItem, - primary, - dropdownAction, - secondary, - 'notebook-cell-run-toolbar', - this.contextMenuService, - { - getKeyBinding: keybindingProvider - }); - actionViewItemDisposables.add(item.onDidChangeDropdownVisibility(visible => { - cellContainer.classList.toggle('cell-run-toolbar-dropdown-active', visible); - })); - - return item; - }, - renderDropdownAsChildElement: true - })); - - return toolbar; - } - - private setupRunToolbar(runButtonContainer: HTMLElement, cellContainer: HTMLElement, contextKeyService: IContextKeyService, disposables: DisposableStore): ToolBar { - const menu = disposables.add(this.menuService.createMenu(this.notebookEditor.creationOptions.menuIds.cellExecutePrimary!, contextKeyService)); - const runToolbar = this.createRunCellToolbar(runButtonContainer, cellContainer, contextKeyService, disposables); - const updateActions = () => { - const actions = this.getCellToolbarActions(menu); - const primary = actions.primary[0]; // Only allow one primary action - runToolbar.setActions(primary ? [primary] : []); - }; - updateActions(); - disposables.add(menu.onDidChange(updateActions)); - disposables.add(this.notebookEditor.notebookOptions.onDidChangeOptions(updateActions)); - return runToolbar; - } - - private updateForOutputs(element: CodeCellViewModel, templateData: CodeCellRenderTemplate): void { - if (element.outputsViewModels.length) { - DOM.show(templateData.focusSinkElement); - } else { - DOM.hide(templateData.focusSinkElement); - } - } - private updateForInternalMetadata(element: CodeCellViewModel, templateData: CodeCellRenderTemplate): void { if (!this.notebookEditor.hasModel()) { return; @@ -885,13 +589,6 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende const internalMetadata = element.internalMetadata; this.updateExecutionOrder(internalMetadata, templateData); - - const progressBar = element.isInputCollapsed ? templateData.collapsedProgressBar : templateData.progressBar; - if (internalMetadata.runState === NotebookCellExecutionState.Executing && !internalMetadata.isPaused) { - progressBar.infinite().show(500); - } else { - progressBar.hide(); - } } private updateForKernel(element: CodeCellViewModel, templateData: CodeCellRenderTemplate): void { @@ -909,47 +606,37 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende } } - private updateForHover(element: CodeCellViewModel, templateData: CodeCellRenderTemplate): void { - templateData.container.classList.toggle('cell-output-hover', element.outputIsHovered); - } - - private updateForFocus(element: CodeCellViewModel, templateData: CodeCellRenderTemplate): void { - templateData.container.classList.toggle('cell-output-focus', element.outputIsFocused); - } - private updateForLayout(element: CodeCellViewModel, templateData: CodeCellRenderTemplate): void { - templateData.disposables.add(DOM.scheduleAtNextAnimationFrame(() => { - const layoutInfo = this.notebookEditor.notebookOptions.getLayoutConfiguration(); + templateData.elementDisposables.add(DOM.scheduleAtNextAnimationFrame(() => { const bottomToolbarDimensions = this.notebookEditor.notebookOptions.computeBottomToolbarDimensions(this.notebookEditor.textModel?.viewType); - templateData.focusIndicatorLeft.setHeight(element.layoutInfo.indicatorHeight); - templateData.focusIndicatorRight.setHeight(element.layoutInfo.indicatorHeight); - templateData.focusIndicatorBottom.domNode.style.transform = `translateY(${element.layoutInfo.totalHeight - bottomToolbarDimensions.bottomToolbarGap - layoutInfo.cellBottomMargin}px)`; + templateData.focusIndicator.updateLayoutNow(element); templateData.outputContainer.setTop(element.layoutInfo.outputContainerOffset); templateData.outputShowMoreContainer.setTop(element.layoutInfo.outputShowMoreContainerOffset); templateData.dragHandle.setHeight(element.layoutInfo.totalHeight - bottomToolbarDimensions.bottomToolbarGap); templateData.container.classList.toggle('cell-statusbar-hidden', this.notebookEditor.notebookOptions.computeEditorStatusbarHeight(element.internalMetadata) === 0); + + this.updateForTitleMenu(templateData); + templateData.betweenCellToolbar.updateLayoutNow(element); })); } + private updateForTitleMenu(templateData: CodeCellRenderTemplate): void { + const layoutInfo = this.notebookEditor.notebookOptions.getLayoutConfiguration(); + if (templateData.titleToolbar.hasActions) { + templateData.focusIndicator.left.domNode.style.transform = `translateY(${layoutInfo.editorToolbarHeight + layoutInfo.cellTopMargin}px)`; + templateData.focusIndicator.right.domNode.style.transform = `translateY(${layoutInfo.editorToolbarHeight + layoutInfo.cellTopMargin}px)`; + } else { + templateData.focusIndicator.left.domNode.style.transform = `translateY(${layoutInfo.cellTopMargin}px)`; + templateData.focusIndicator.right.domNode.style.transform = `translateY(${layoutInfo.cellTopMargin}px)`; + } + } + renderElement(element: CodeCellViewModel, index: number, templateData: CodeCellRenderTemplate, height: number | undefined): void { if (!this.notebookEditor.hasModel()) { throw new Error('The notebook editor is not attached with view model yet.'); } - const removedClassNames: string[] = []; - templateData.rootContainer.classList.forEach(className => { - if (/^nb\-.*$/.test(className)) { - removedClassNames.push(className); - } - }); - - removedClassNames.forEach(className => { - templateData.rootContainer.classList.remove(className); - }); - - templateData.decorationContainer.innerText = ''; - this.commonRenderElement(element, templateData); templateData.currentRenderedCell = element; @@ -959,40 +646,26 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende } templateData.outputContainer.domNode.innerText = ''; - const cellOutputCollapsedContainer = DOM.append(templateData.outputContainer.domNode, $('.output-collapse-container')); - templateData.cellOutputCollapsedContainer = cellOutputCollapsedContainer; - this.setupOutputCollapsedPart(templateData, cellOutputCollapsedContainer, element); + templateData.outputContainer.domNode.appendChild(templateData.cellOutputCollapsedContainer); const elementDisposables = templateData.elementDisposables; + const cellEditorOptions = elementDisposables.add(new CellEditorOptions(this.notebookEditor, this.notebookEditor.notebookOptions, this.configurationService, element.language)); - const generateCellTopDecorations = () => { - templateData.decorationContainer.innerText = ''; + elementDisposables.add(templateData.instantiationService.createInstance(CodeCell, this.notebookEditor, element, templateData, [ + templateData.focusIndicator, + templateData.betweenCellToolbar, + templateData.statusBar, + templateData.progressBar, + templateData.titleToolbar, + templateData.runToolbar, + cellEditorOptions + ])); - element.getCellDecorations().filter(options => options.topClassName !== undefined).forEach(options => { - templateData.decorationContainer.append(DOM.$(`.${options.topClassName!}`)); - }); - }; - - elementDisposables.add(element.onCellDecorationsChanged((e) => { - const modified = e.added.find(e => e.topClassName) || e.removed.find(e => e.topClassName); - - if (modified) { - generateCellTopDecorations(); - } - })); - - generateCellTopDecorations(); - - const child = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, templateData.contextKeyService])); - elementDisposables.add(child.createInstance(CodeCell, this.notebookEditor, element, templateData)); this.renderedEditors.set(element, templateData.editor); - const cellEditorOptions = new CellEditorOptions(this.notebookEditor, this.notebookEditor.notebookOptions, this.configurationService, element.language); - elementDisposables.add(cellEditorOptions); elementDisposables.add(cellEditorOptions.onDidChange(() => templateData.editor.updateOptions(cellEditorOptions.getUpdatedValue(element.internalMetadata)))); templateData.editor.updateOptions(cellEditorOptions.getUpdatedValue(element.internalMetadata)); - - elementDisposables.add(new CellContextKeyManager(templateData.contextKeyService, this.notebookEditor, element)); + cellEditorOptions.setLineNumbers(element.lineNumbers); this.updateForLayout(element, templateData); elementDisposables.add(element.onDidChangeLayout(() => { @@ -1000,61 +673,23 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende })); this.updateForInternalMetadata(element, templateData); - this.updateForHover(element, templateData); - this.updateForFocus(element, templateData); - cellEditorOptions.setLineNumbers(element.lineNumbers); + elementDisposables.add(element.onDidChangeState((e) => { if (e.metadataChanged || e.internalMetadataChanged) { this.updateForInternalMetadata(element, templateData); - this.updateForLayout(element, templateData); - } - - if (e.outputIsHoveredChanged) { - this.updateForHover(element, templateData); - } - - if (e.outputIsFocusedChanged) { - this.updateForFocus(element, templateData); - } - - if (e.cellLineNumberChanged) { - cellEditorOptions.setLineNumbers(element.lineNumbers); - } - - if (e.inputCollapsedChanged) { - if (element.isInputCollapsed) { - templateData.progressBar.hide(); - } else { - templateData.collapsedProgressBar.hide(); - } } })); - this.updateForOutputs(element, templateData); - elementDisposables.add(element.onDidChangeOutputs(_e => this.updateForOutputs(element, templateData))); - this.updateForKernel(element, templateData); - this.setupCellToolbarActions(templateData, elementDisposables); - - const toolbarContext = { - ui: true, - cell: element, - cellTemplate: templateData, - notebookEditor: this.notebookEditor, - $mid: MarshalledId.NotebookCellActionContext - }; - templateData.toolbar.context = toolbarContext; - templateData.runToolbar.context = toolbarContext; - templateData.deleteToolbar.context = toolbarContext; - - this.setBetweenCellToolbarContext(templateData, element, toolbarContext); - - templateData.statusBar.update(toolbarContext); + templateData.elementDisposables.add(templateData.titleToolbar.onDidUpdateActions(() => { + // Don't call directly here - is initially called by updateForLayout in the next frame + this.updateForTitleMenu(templateData); + })); } disposeTemplate(templateData: CodeCellRenderTemplate): void { - templateData.disposables.clear(); + templateData.templateDisposables.clear(); } disposeElement(element: ICellViewModel, index: number, templateData: CodeCellRenderTemplate, height: number | undefined): void { @@ -1062,96 +697,3 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende this.renderedEditors.delete(element); } } - -export function getCodeCellExecutionContextKeyService(contextKeyService: IContextKeyService): IContextKeyService { - // Create a fake ContextKeyService, and look up the keybindings within this context. - const executionContextKeyService = contextKeyService.createScoped(document.createElement('div')); - InputFocusedContext.bindTo(executionContextKeyService).set(true); - EditorContextKeys.editorTextFocus.bindTo(executionContextKeyService).set(true); - EditorContextKeys.focus.bindTo(executionContextKeyService).set(true); - EditorContextKeys.textInputFocus.bindTo(executionContextKeyService).set(true); - NOTEBOOK_CELL_EXECUTION_STATE.bindTo(executionContextKeyService).set('idle'); - NOTEBOOK_CELL_LIST_FOCUSED.bindTo(executionContextKeyService).set(true); - NOTEBOOK_EDITOR_FOCUSED.bindTo(executionContextKeyService).set(true); - NOTEBOOK_CELL_TYPE.bindTo(executionContextKeyService).set('code'); - - return executionContextKeyService; -} - -export class ListTopCellToolbar extends Disposable { - private topCellToolbar: HTMLElement; - private menu: IMenu; - private toolbar: ToolBar; - private readonly _modelDisposables = this._register(new DisposableStore()); - constructor( - protected readonly notebookEditor: INotebookEditorDelegate, - - contextKeyService: IContextKeyService, - insertionIndicatorContainer: HTMLElement, - @IInstantiationService protected readonly instantiationService: IInstantiationService, - @IContextMenuService protected readonly contextMenuService: IContextMenuService, - @IMenuService protected readonly menuService: IMenuService - ) { - super(); - - this.topCellToolbar = DOM.append(insertionIndicatorContainer, $('.cell-list-top-cell-toolbar-container')); - - this.toolbar = this._register(new ToolBar(this.topCellToolbar, this.contextMenuService, { - actionViewItemProvider: action => { - if (action instanceof MenuItemAction) { - const item = this.instantiationService.createInstance(CodiconActionViewItem, action); - return item; - } - - return undefined; - } - })); - this.toolbar.context = { - notebookEditor - }; - - this.menu = this._register(this.menuService.createMenu(this.notebookEditor.creationOptions.menuIds.cellTopInsertToolbar, contextKeyService)); - this._register(this.menu.onDidChange(() => { - this.updateActions(); - })); - this.updateActions(); - - // update toolbar container css based on cell list length - this._register(this.notebookEditor.onDidChangeModel(() => { - this._modelDisposables.clear(); - - if (this.notebookEditor.hasModel()) { - this._modelDisposables.add(this.notebookEditor.onDidChangeViewCells(() => { - this.updateClass(); - })); - - this.updateClass(); - } - })); - - this.updateClass(); - } - - private updateActions() { - const actions = this.getCellToolbarActions(this.menu, false); - this.toolbar.setActions(actions.primary, actions.secondary); - } - - private updateClass() { - if (this.notebookEditor.hasModel() && this.notebookEditor.getLength() === 0) { - this.topCellToolbar.classList.add('emptyNotebook'); - } else { - this.topCellToolbar.classList.remove('emptyNotebook'); - } - } - - private getCellToolbarActions(menu: IMenu, alwaysFillSecondaryActions: boolean): { primary: IAction[], secondary: IAction[]; } { - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; - - createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, result, g => /^inline/.test(g)); - - return result; - } -} diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts index 52430aabb78..ed62d87c052 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts @@ -83,6 +83,11 @@ export interface IClickMarkupCellMessage extends BaseToWebviewMessage { readonly shiftKey: boolean; } +export interface IClickedLinkMessage extends BaseToWebviewMessage { + readonly type: 'clicked-link'; + readonly href: string; +} + export interface IContextMenuMarkupCellMessage extends BaseToWebviewMessage { readonly type: 'contextMenuMarkupCell'; readonly cellId: string; @@ -373,6 +378,7 @@ export type FromWebviewMessage = WebviewInitialized | ICustomRendererMessage | IClickedDataUrlMessage | IClickMarkupCellMessage | + IClickedLinkMessage | IContextMenuMarkupCellMessage | IMouseEnterMarkupCellMessage | IMouseLeaveMarkupCellMessage | diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index df12a174b99..46434821fab 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -96,9 +96,15 @@ async function webviewPreloads(ctx: PreloadContext) { postNotebookMessage('scroll-to-reveal', { scrollTop }); return; } + } else { + const href = node.getAttribute('href'); + if (href) { + postNotebookMessage('clicked-link', { href }); + } } event.preventDefault(); + event.stopPropagation(); return; } } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts index fabc8219d80..9c022a93c24 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts @@ -104,8 +104,10 @@ export abstract class BaseCellViewModel extends Disposable { return this._focusMode; } set focusMode(newMode: CellFocusMode) { - this._focusMode = newMode; - this._onDidChangeState.fire({ focusModeChanged: true }); + if (this._focusMode !== newMode) { + this._focusMode = newMode; + this._onDidChangeState.fire({ focusModeChanged: true }); + } } protected _textEditor?: ICodeEditor; diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts index 2a32670df98..7cc4f3303c1 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts @@ -22,16 +22,27 @@ import { registerThemingParticipant } from 'vs/platform/theme/common/themeServic import { SELECT_KERNEL_ID } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { INotebookEditorDelegate, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebooKernelActionViewItem } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem'; -import { ActionViewWithLabel } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellActionView'; +import { ActionViewWithLabel } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView'; import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions'; interface IActionModel { - action: IAction; size: number; visible: boolean; + action: IAction; + size: number; + visible: boolean; + renderLabel: boolean; } +enum RenderLabel { + Always = 0, + Never = 1, + Dynamic = 2 +} + +type RenderLabelWithFallback = true | false | 'always' | 'never' | 'dynamic'; + const TOGGLE_MORE_ACTION_WIDTH = 21; const ACTION_PADDING = 8; @@ -46,7 +57,7 @@ export class NotebookEditorToolbar extends Disposable { private _secondaryActions: IAction[]; private _notebookRightToolbar!: ToolBar; private _useGlobalToolbar: boolean = false; - private _renderLabel: boolean = true; + private _renderLabel: RenderLabel = RenderLabel.Always; private readonly _onDidChangeState = this._register(new Emitter()); onDidChangeState: Event = this._onDidChangeState.event; @@ -114,7 +125,7 @@ export class NotebookEditorToolbar extends Disposable { this._register(this._notebookGlobalActionsMenu); this._useGlobalToolbar = this.notebookOptions.getLayoutConfiguration().globalToolbar; - this._renderLabel = this.configurationService.getValue(NotebookSetting.globalToolbarShowLabel); + this._renderLabel = this._convertConfiguration(this.configurationService.getValue(NotebookSetting.globalToolbarShowLabel)); const context = { ui: true, @@ -127,8 +138,13 @@ export class NotebookEditorToolbar extends Disposable { return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor); } - if (this._renderLabel) { - return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action) : undefined; + if (this._renderLabel !== RenderLabel.Never) { + const a = this._primaryActions.find(a => a.action.id === action.id); + if (a && a.renderLabel) { + return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action) : undefined; + } else { + return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) : undefined; + } } else { return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) : undefined; } @@ -185,7 +201,7 @@ export class NotebookEditorToolbar extends Disposable { this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(NotebookSetting.globalToolbarShowLabel)) { - this._renderLabel = this.configurationService.getValue(NotebookSetting.globalToolbarShowLabel); + this._renderLabel = this._convertConfiguration(this.configurationService.getValue(NotebookSetting.globalToolbarShowLabel)); const oldElement = this._notebookLeftToolbar.getElement(); oldElement.parentElement?.removeChild(oldElement); this._notebookLeftToolbar.dispose(); @@ -214,6 +230,21 @@ export class NotebookEditorToolbar extends Disposable { } } + private _convertConfiguration(value: RenderLabelWithFallback): RenderLabel { + switch (value) { + case true: + return RenderLabel.Always; + case false: + return RenderLabel.Never; + case 'always': + return RenderLabel.Always; + case 'never': + return RenderLabel.Never; + case 'dynamic': + return RenderLabel.Dynamic; + } + } + private _showNotebookActionsinEditorToolbar() { // when there is no view model, just ignore. if (!this.notebookEditor.hasModel()) { @@ -223,48 +254,52 @@ export class NotebookEditorToolbar extends Disposable { if (!this._useGlobalToolbar) { this.domNode.style.display = 'none'; } else { - const groups = this._notebookGlobalActionsMenu.getActions({ shouldForwardArgs: true, renderShortTitle: true }); - this.domNode.style.display = 'flex'; - const primaryLeftGroups = groups.filter(group => /^navigation/.test(group[0])); - let primaryActions: IAction[] = []; - primaryLeftGroups.sort((a, b) => { - if (a[0] === 'navigation') { - return 1; - } - - if (b[0] === 'navigation') { - return -1; - } - - return 0; - }).forEach((group, index) => { - primaryActions.push(...group[1]); - if (index < primaryLeftGroups.length - 1) { - primaryActions.push(new Separator()); - } - }); - const primaryRightGroup = groups.find(group => /^status/.test(group[0])); - const primaryRightActions = primaryRightGroup ? primaryRightGroup[1] : []; - const secondaryActions = groups.filter(group => !/^navigation/.test(group[0]) && !/^status/.test(group[0])).reduce((prev: (MenuItemAction | SubmenuItemAction)[], curr) => { prev.push(...curr[1]); return prev; }, []); - - this._notebookLeftToolbar.setActions([], []); - - this._notebookLeftToolbar.setActions(primaryActions, secondaryActions); - this._notebookRightToolbar.setActions(primaryRightActions, []); - this._secondaryActions = secondaryActions; - // flush to make sure it can be updated later - this._primaryActions = []; - - if (this._dimension && this._dimension.width >= 0 && this._dimension.height >= 0) { - this._cacheItemSizes(this._notebookLeftToolbar); - } - - this._computeSizes(); + this._setNotebookActions(); } this._onDidChangeState.fire(); } + private _setNotebookActions() { + const groups = this._notebookGlobalActionsMenu.getActions({ shouldForwardArgs: true, renderShortTitle: true }); + this.domNode.style.display = 'flex'; + const primaryLeftGroups = groups.filter(group => /^navigation/.test(group[0])); + let primaryActions: IAction[] = []; + primaryLeftGroups.sort((a, b) => { + if (a[0] === 'navigation') { + return 1; + } + + if (b[0] === 'navigation') { + return -1; + } + + return 0; + }).forEach((group, index) => { + primaryActions.push(...group[1]); + if (index < primaryLeftGroups.length - 1) { + primaryActions.push(new Separator()); + } + }); + const primaryRightGroup = groups.find(group => /^status/.test(group[0])); + const primaryRightActions = primaryRightGroup ? primaryRightGroup[1] : []; + const secondaryActions = groups.filter(group => !/^navigation/.test(group[0]) && !/^status/.test(group[0])).reduce((prev: (MenuItemAction | SubmenuItemAction)[], curr) => { prev.push(...curr[1]); return prev; }, []); + + this._notebookLeftToolbar.setActions([], []); + + this._notebookLeftToolbar.setActions(primaryActions, secondaryActions); + this._notebookRightToolbar.setActions(primaryRightActions, []); + this._secondaryActions = secondaryActions; + // flush to make sure it can be updated later + this._primaryActions = []; + + if (this._dimension && this._dimension.width >= 0 && this._dimension.height >= 0) { + this._cacheItemSizes(this._notebookLeftToolbar); + } + + this._computeSizes(); + } + private _cacheItemSizes(toolbar: ToolBar) { let actions: IActionModel[] = []; @@ -273,7 +308,8 @@ export class NotebookEditorToolbar extends Disposable { actions.push({ action: action, size: toolbar.getItemWidth(i), - visible: true + visible: true, + renderLabel: true }); } @@ -305,39 +341,133 @@ export class NotebookEditorToolbar extends Disposable { const kernelWidth = (rightToolbar.getItemsLength() ? rightToolbar.getItemWidth(0) : 0) + ACTION_PADDING; if (this._canBeVisible(this._dimension.width - kernelWidth - ACTION_PADDING /** left margin */)) { - this._primaryActions.forEach(action => action.visible = true); + this._primaryActions.forEach(action => { + action.visible = true; + action.renderLabel = true; + }); toolbar.setActions(this._primaryActions.filter(action => action.action.id !== ToggleMenuAction.ID).map(model => model.action), this._secondaryActions); return; } const leftToolbarContainerMaxWidth = this._dimension.width - kernelWidth - (TOGGLE_MORE_ACTION_WIDTH + ACTION_PADDING) /** ... */ - ACTION_PADDING /** toolbar left margin */; - const lastItemInLeft = this._primaryActions[this._primaryActions.length - 1]; - const hasToggleMoreAction = lastItemInLeft.action.id === ToggleMenuAction.ID; - - let size = 0; - let actions: IActionModel[] = []; - - for (let i = 0; i < this._primaryActions.length - (hasToggleMoreAction ? 1 : 0); i++) { - const actionModel = this._primaryActions[i]; - - const itemSize = actionModel.size; - if (size + itemSize <= leftToolbarContainerMaxWidth) { - size += ACTION_PADDING + itemSize; - actions.push(actionModel); - } else { - break; - } + if (this._renderLabel === RenderLabel.Dynamic) { + this._calculateDynamicLabel(leftToolbarContainerMaxWidth); + } else { + this._calcuateFixedLabel(leftToolbarContainerMaxWidth); } - - actions.forEach(action => action.visible = true); - this._primaryActions.slice(actions.length).forEach(action => action.visible = false); - - toolbar.setActions( - actions.filter(action => (action.visible && action.action.id !== ToggleMenuAction.ID)).map(action => action.action), - [...this._primaryActions.slice(actions.length).filter(action => !action.visible && action.action.id !== ToggleMenuAction.ID).map(action => action.action), ...this._secondaryActions]); } } + private _calcuateFixedLabel(leftToolbarContainerMaxWidth: number) { + const lastItemInLeft = this._primaryActions[this._primaryActions.length - 1]; + const hasToggleMoreAction = lastItemInLeft.action.id === ToggleMenuAction.ID; + + let size = 0; + let actions: IActionModel[] = []; + + for (let i = 0; i < this._primaryActions.length - (hasToggleMoreAction ? 1 : 0); i++) { + const actionModel = this._primaryActions[i]; + + const itemSize = actionModel.size; + if (size + itemSize <= leftToolbarContainerMaxWidth) { + size += ACTION_PADDING + itemSize; + actions.push(actionModel); + } else { + break; + } + } + + actions.forEach(action => action.visible = true); + this._primaryActions.slice(actions.length).forEach(action => action.visible = false); + + this._notebookLeftToolbar.setActions( + actions.filter(action => (action.visible && action.action.id !== ToggleMenuAction.ID)).map(action => action.action), + [...this._primaryActions.slice(actions.length).filter(action => !action.visible && action.action.id !== ToggleMenuAction.ID).map(action => action.action), ...this._secondaryActions]); + } + + private _calculateDynamicLabel(leftToolbarContainerMaxWidth: number) { + const lastItemInLeft = this._primaryActions[this._primaryActions.length - 1]; + const hasToggleMoreAction = lastItemInLeft.action.id === ToggleMenuAction.ID; + const actions = this._primaryActions.slice(0, this._primaryActions.length - (hasToggleMoreAction ? 1 : 0)); + + if (actions.length === 0) { + this._notebookLeftToolbar.setActions(this._primaryActions.filter(action => (action.visible && action.action.id !== ToggleMenuAction.ID)).map(action => action.action), this._secondaryActions); + return; + } + + let totalWidthWithLabels = actions.map(action => action.size).reduce((a, b) => a + b, 0) + (actions.length - 1) * ACTION_PADDING; + if (totalWidthWithLabels <= leftToolbarContainerMaxWidth) { + this._primaryActions.forEach(action => { + action.visible = true; + action.renderLabel = true; + }); + this._notebookLeftToolbar.setActions(this._primaryActions.filter(action => (action.visible && action.action.id !== ToggleMenuAction.ID)).map(action => action.action), this._secondaryActions); + return; + } + + // too narrow, we need to hide some labels + + if ((actions.length * 21 + (actions.length - 1) * ACTION_PADDING) > leftToolbarContainerMaxWidth) { + this._calcuateWithAlllabelsHidden(actions, leftToolbarContainerMaxWidth); + return; + } + + const sums = []; + let sum = 0; + let lastActionWithLabel = -1; + for (let i = 0; i < actions.length; i++) { + sum += actions[i].size; + sums.push(sum); + + if (actions[i].action instanceof Separator) { + // find group separator + const remainingItems = actions.slice(i + 1); + const newTotalSum = sum + (remainingItems.length === 0 ? 0 : (remainingItems.length * 21 + (remainingItems.length - 1) * ACTION_PADDING)); + if (newTotalSum <= leftToolbarContainerMaxWidth) { + lastActionWithLabel = i; + } + } else { + continue; + } + } + + if (lastActionWithLabel < 0) { + this._calcuateWithAlllabelsHidden(actions, leftToolbarContainerMaxWidth); + return; + } + + const visibleActions = actions.slice(0, lastActionWithLabel + 1); + visibleActions.forEach(action => { action.visible = true; action.renderLabel = true; }); + this._primaryActions.slice(visibleActions.length).forEach(action => { action.visible = true; action.renderLabel = false; }); + this._notebookLeftToolbar.setActions(this._primaryActions.filter(action => (action.visible && action.action.id !== ToggleMenuAction.ID)).map(action => action.action), this._secondaryActions); + } + + private _calcuateWithAlllabelsHidden(actions: IActionModel[], leftToolbarContainerMaxWidth: number) { + // all actions hidden labels + this._primaryActions.forEach(action => { action.renderLabel = false; }); + let size = 0; + let renderActions: IActionModel[] = []; + + for (let i = 0; i < actions.length; i++) { + const actionModel = actions[i]; + + const itemSize = 21; + if (size + itemSize <= leftToolbarContainerMaxWidth) { + size += ACTION_PADDING + itemSize; + renderActions.push(actionModel); + } else { + break; + } + } + + actions.forEach(action => action.visible = true); + this._primaryActions.slice(actions.length).forEach(action => action.visible = false); + + this._notebookLeftToolbar.setActions( + actions.filter(action => (action.visible && action.action.id !== ToggleMenuAction.ID)).map(action => action.action), + [...this._primaryActions.slice(actions.length).filter(action => !action.visible && action.action.id !== ToggleMenuAction.ID).map(action => action.action), ...this._secondaryActions]); + } + layout(dimension: DOM.Dimension) { this._dimension = dimension; diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts index 9fb10676a97..de591ff05a7 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts @@ -64,7 +64,7 @@ export class NotebooKernelActionViewItem extends ActionViewItem { private _updateActionFromKernelInfo(info: INotebookKernelMatchResult): void { this._action.enabled = true; - const selectedOrSuggested = info.selected ?? info.suggestions[0]; + const selectedOrSuggested = info.selected ?? (info.all.length === 1 && info.suggestions.length === 1 ? info.suggestions[0] : undefined); if (selectedOrSuggested) { // selected or suggested kernel this._action.label = selectedOrSuggested.label; diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts new file mode 100644 index 00000000000..33ac46a5319 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts @@ -0,0 +1,97 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as DOM from 'vs/base/browser/dom'; +import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; +import { IAction } from 'vs/base/common/actions'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenu, IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { INotebookActionContext } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; +import { INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView'; + +export class ListTopCellToolbar extends Disposable { + private topCellToolbar: HTMLElement; + private menu: IMenu; + private toolbar: ToolBar; + private readonly _modelDisposables = this._register(new DisposableStore()); + constructor( + protected readonly notebookEditor: INotebookEditorDelegate, + + contextKeyService: IContextKeyService, + insertionIndicatorContainer: HTMLElement, + @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IContextMenuService protected readonly contextMenuService: IContextMenuService, + @IMenuService protected readonly menuService: IMenuService + ) { + super(); + + this.topCellToolbar = DOM.append(insertionIndicatorContainer, DOM.$('.cell-list-top-cell-toolbar-container')); + + this.toolbar = this._register(new ToolBar(this.topCellToolbar, this.contextMenuService, { + actionViewItemProvider: action => { + if (action instanceof MenuItemAction) { + const item = this.instantiationService.createInstance(CodiconActionViewItem, action); + return item; + } + + return undefined; + } + })); + this.toolbar.context = { + notebookEditor + }; + + this.menu = this._register(this.menuService.createMenu(this.notebookEditor.creationOptions.menuIds.cellTopInsertToolbar, contextKeyService)); + this._register(this.menu.onDidChange(() => { + this.updateActions(); + })); + this.updateActions(); + + // update toolbar container css based on cell list length + this._register(this.notebookEditor.onDidChangeModel(() => { + this._modelDisposables.clear(); + + if (this.notebookEditor.hasModel()) { + this._modelDisposables.add(this.notebookEditor.onDidChangeViewCells(() => { + this.updateClass(); + })); + + this.updateClass(); + } + })); + + this.updateClass(); + } + + private updateActions() { + const actions = this.getCellToolbarActions(this.menu, false); + this.toolbar.setActions(actions.primary, actions.secondary); + } + + private updateClass() { + if (this.notebookEditor.hasModel() && this.notebookEditor.getLength() === 0) { + this.topCellToolbar.classList.add('emptyNotebook'); + } else { + this.topCellToolbar.classList.remove('emptyNotebook'); + } + } + + private getCellToolbarActions(menu: IMenu, alwaysFillSecondaryActions: boolean): { primary: IAction[], secondary: IAction[]; } { + type NewType = IAction; + + const primary: NewType[] = []; + const secondary: IAction[] = []; + const result = { primary, secondary }; + + createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, result, g => /^inline/.test(g)); + + return result; + } +} diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index c495315f175..63dbfdff771 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -57,14 +57,14 @@ export const ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER: readonly string[] = [ ]; /** - * A mapping of extension IDs who contain renderers, to extensions who they + * A mapping of extension IDs who contain renderers, to notebook ids who they * should be treated as the same in the renderer selection logic. This is used * to prefer the 1st party Jupyter renderers even though they're in a separate * extension, for instance. See #136247. */ export const RENDERER_EQUIVALENT_EXTENSIONS: ReadonlyMap> = new Map([ - ['ms-toolsai.jupyter', new Set(['vscode.ipynb'])], - ['ms-toolsai.jupyter-renderers', new Set(['vscode.ipynb'])], + ['ms-toolsai.jupyter', new Set(['jupyter-notebook', 'interactive'])], + ['ms-toolsai.jupyter-renderers', new Set(['jupyter-notebook', 'interactive'])], ]); export const BUILTIN_RENDERER_ID = '_builtin'; @@ -521,19 +521,30 @@ export namespace CellUri { }; } - export function parseCellMetadataUri(metadata: URI) { - if (metadata.scheme !== Schemas.vscodeNotebookCellMetadata) { - return undefined; + export function generateCellOutputUri(notebook: URI, handle: number, outputId?: string) { + return notebook.with({ + scheme: Schemas.vscodeNotebookCellOutput, + fragment: `ch${handle.toString().padStart(7, '0')},${outputId ?? ''},${notebook.scheme !== Schemas.file ? notebook.scheme : ''}` + }); + } + + export function parseCellOutputUri(uri: URI): { notebook: URI, handle: number; outputId?: string } | undefined { + if (uri.scheme !== Schemas.vscodeNotebookCellOutput) { + return; } - const match = _regex.exec(metadata.fragment); + + const match = /^ch(\d{7,})\,([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})?\,(.*)$/i.exec(uri.fragment); if (!match) { return undefined; } const handle = Number(match[1]); + const outputId = (match[2] && match[2] !== '') ? match[2] : undefined; + const scheme = match[3]; return { handle, - notebook: metadata.with({ - scheme: metadata.fragment.substr(match[0].length) || Schemas.file, + outputId, + notebook: uri.with({ + scheme: scheme || Schemas.file, fragment: null }) }; diff --git a/src/vs/workbench/contrib/notebook/test/cellOutput.test.ts b/src/vs/workbench/contrib/notebook/test/cellOutput.test.ts index 2c4593d6de9..5354a020e95 100644 --- a/src/vs/workbench/contrib/notebook/test/cellOutput.test.ts +++ b/src/vs/workbench/contrib/notebook/test/cellOutput.test.ts @@ -16,7 +16,7 @@ import { ICellOutputViewModel, IRenderOutput, RenderOutputType } from 'vs/workbe import { CodeCellRenderTemplate, IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; import { OutputRendererRegistry } from 'vs/workbench/contrib/notebook/browser/view/output/rendererRegistry'; import { getStringValue } from 'vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform'; -import { CellOutputContainer } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellOutput'; +import { CellOutputContainer } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { BUILTIN_RENDERER_ID, CellEditType, CellKind, IOutputDto, IOutputItemDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -110,7 +110,7 @@ suite('NotebookViewModel Outputs', async () => { return 100; } }, - disposables: new DisposableStore(), + templateDisposables: new DisposableStore(), } as unknown as CodeCellRenderTemplate, { limit: 5 }, openerService, instantiationService); container.render(100); assert.strictEqual(container.renderedOutputEntries.length, 4); @@ -189,7 +189,7 @@ suite('NotebookViewModel Outputs', async () => { return 100; } }, - disposables: new DisposableStore(), + templateDisposables: new DisposableStore(), } as unknown as CodeCellRenderTemplate, { limit: 5 }, openerService, instantiationService); container.render(100); assert.strictEqual(container.renderedOutputEntries.length, 5); diff --git a/src/vs/workbench/contrib/output/browser/outputView.ts b/src/vs/workbench/contrib/output/browser/outputView.ts index 9db7a355a31..3ebd3590841 100644 --- a/src/vs/workbench/contrib/output/browser/outputView.ts +++ b/src/vs/workbench/contrib/output/browser/outputView.ts @@ -271,8 +271,7 @@ export class OutputEditor extends AbstractTextResourceEditor { class SwitchOutputActionViewItem extends SelectActionViewItem { - // allow-any-unicode-next-line - private static readonly SEPARATOR = '─────────'; + private static readonly SEPARATOR = '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'; private outputChannels: IOutputChannelDescriptor[] = []; private logChannels: IOutputChannelDescriptor[] = []; diff --git a/src/vs/workbench/contrib/preferences/browser/media/keybindingsEditor.css b/src/vs/workbench/contrib/preferences/browser/media/keybindingsEditor.css index b2553a8dd68..f6d027c3373 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/keybindingsEditor.css +++ b/src/vs/workbench/contrib/preferences/browser/media/keybindingsEditor.css @@ -21,13 +21,13 @@ position: absolute; top: 0; right: 10px; - margin-top: 5px; + margin-top: 4px; display: flex; } .keybindings-editor > .keybindings-header > .search-container > .keybindings-search-actions-container > .recording-badge { margin-right: 8px; - padding: 3px; + padding: 4px; } .keybindings-editor > .keybindings-header.small > .search-container > .keybindings-search-actions-container > .recording-badge, @@ -43,12 +43,17 @@ .keybindings-editor > .keybindings-header > .search-container > .keybindings-search-actions-container .monaco-action-bar .action-item { margin-right: 4px; } +.keybindings-editor .monaco-action-bar .action-item .monaco-custom-checkbox { + margin: 0; + padding: 2px; +} .keybindings-editor .monaco-action-bar .action-item > .codicon { display: flex; align-items: center; justify-content: center; color: inherit; + box-sizing: content-box; } .keybindings-editor > .keybindings-header .open-keybindings-container { diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index 5c283256fdf..7374a24b8a5 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -24,7 +24,7 @@ import * as modes from 'vs/editor/common/modes'; import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import * as nls from 'vs/nls'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationRegistry, overrideIdentifierFromKey, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; +import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationRegistry, overrideIdentifiersFromKey, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IMarkerData, IMarkerService, MarkerSeverity, MarkerTag } from 'vs/platform/markers/common/markers'; @@ -76,9 +76,9 @@ export class UserSettingsRenderer extends Disposable implements IPreferencesRend } updatePreference(key: string, value: any, source: IIndexedSetting): void { - const overrideIdentifier = source.overrideOf ? overrideIdentifierFromKey(source.overrideOf.key) : null; + const overrideIdentifiers = source.overrideOf ? overrideIdentifiersFromKey(source.overrideOf.key) : null; const resource = this.preferencesModel.uri; - this.configurationService.updateValue(key, value, { overrideIdentifier, resource }, this.preferencesModel.configurationTarget) + this.configurationService.updateValue(key, value, { overrideIdentifiers, resource }, this.preferencesModel.configurationTarget) .then(() => this.onSettingUpdated(source)); } @@ -533,7 +533,7 @@ class UnsupportedSettingsRenderer extends Disposable implements modes.CodeAction this.handleWorkspaceFolderConfiguration(setting, configuration, markerData); break; } - } else if (!OVERRIDE_PROPERTY_PATTERN.test(setting.key)) { // Ignore override settings (language specific settings) + } else if (!OVERRIDE_PROPERTY_REGEX.test(setting.key)) { // Ignore override settings (language specific settings) markerData.push({ severity: MarkerSeverity.Hint, tags: [MarkerTag.Unnecessary], diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index f12bf278726..21ab9a92d6e 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -1181,8 +1181,7 @@ export class SettingsEditor2 extends EditorPane { const query = this.searchWidget.getValue().trim(); this.delayedFilterLogging.cancel(); - // allow-any-unicode-next-line - await this.triggerSearch(query.replace(/›/g, ' ')); + await this.triggerSearch(query.replace(/\u203A/g, ' ')); if (query && this.searchResultModel) { this.delayedFilterLogging.trigger(() => this.reportFilteringUsed(query, this.searchResultModel!.getUniqueResults())); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 88f090a63ac..475c52528f2 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -435,34 +435,53 @@ export async function resolveExtensionsSettings(extensionService: IExtensionServ addEntryToTree(extensionId, extensionName, childEntry); }; - const processPromises = groups - .sort((a, b) => a.title.localeCompare(b.title)) - .map(g => processGroupEntry(g)); - + const processPromises = groups.map(g => processGroupEntry(g)); return Promise.all(processPromises).then(() => { const extGroups: ITOCEntry[] = []; - for (const value of extGroupTree.values()) { - for (const child of value.children!) { - // Sort the individual settings + for (const extensionRootEntry of extGroupTree.values()) { + for (const child of extensionRootEntry.children!) { + // Sort the individual settings of the child. child.settings?.sort((a, b) => { return compareNullableIntegers(a.order, b.order); }); } - if (value.children!.length === 1) { - // Push a flattened setting + + if (extensionRootEntry.children!.length === 1) { + // There is a single category for this extension. + // Push a flattened setting. extGroups.push({ - id: value.id, - label: value.children![0].label, - settings: value.children![0].settings + id: extensionRootEntry.id, + label: extensionRootEntry.children![0].label, + settings: extensionRootEntry.children![0].settings }); } else { - value.children!.sort((a, b) => { + // Sort the categories. + extensionRootEntry.children!.sort((a, b) => { return compareNullableIntegers(a.order, b.order); }); - extGroups.push(value); + + // If there is a category that matches the setting name, + // add the settings in manually as "ungrouped" settings. + // https://github.com/microsoft/vscode/issues/137259 + const ungroupedChild = extensionRootEntry.children!.find(child => child.label === extensionRootEntry.label); + if (ungroupedChild && !ungroupedChild.children) { + const groupedChildren = extensionRootEntry.children!.filter(child => child !== ungroupedChild); + extGroups.push({ + id: extensionRootEntry.id, + label: extensionRootEntry.label, + settings: ungroupedChild.settings, + children: groupedChildren + }); + } else { + // Push all the groups as-is. + extGroups.push(extensionRootEntry); + } } } + // Sort the outermost settings. + extGroups.sort((a, b) => a.label.localeCompare(b.label)); + return { id: 'extensions', label: localize('extensions', "Extensions"), diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index f70a5afeff2..ea6967e593d 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -521,8 +521,7 @@ export function settingKeyToDisplayFormat(key: string, groupId = ''): { category function wordifyKey(key: string): string { key = key - // allow-any-unicode-next-line - .replace(/\.([a-z0-9])/g, (_, p1) => ` › ${p1.toUpperCase()}`) // Replace dot with spaced '>' + .replace(/\.([a-z0-9])/g, (_, p1) => ` \u203A ${p1.toUpperCase()}`) // Replace dot with spaced '>' .replace(/([a-z0-9])([A-Z])/g, '$1 $2') // Camel case to spacing, fooBar => foo Bar .replace(/^[a-z]/g, match => match.toUpperCase()) // Upper casing all first letters, foo => Foo .replace(/\b\w+\b/g, match => { // Upper casing known acronyms diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index 4d48e2e58ca..488f5665aa9 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -498,7 +498,7 @@ export class RemoteViewPaneContainer extends FilterViewPaneContainer implements } private _handleRemoteInfoExtensionPoint(extension: IExtensionPointUser, helpInformation: HelpInformation[]) { - if (!isProposedApiEnabled(extension.description)) { + if (!isProposedApiEnabled(extension.description, 'contribRemoteHelp')) { return; } diff --git a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts index 359a8a983f9..d4a8b77576f 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts @@ -254,7 +254,7 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr } private validatedGroup(group: string) { - if (!group.match(/^(remote|virtualfs)_(\d\d)_(([a-z][a-z0-9+\-.]*)_(.*))$/)) { + if (!group.match(/^(remote|virtualfs)_(\d\d)_(([a-z][a-z0-9+.-]*)_(.*))$/)) { if (!this.loggedInvalidGroupNames[group]) { this.loggedInvalidGroupNames[group] = true; this.logService.warn(`Invalid group name used in "statusBar/remoteIndicator" menu contribution: ${group}. Entries ignored. Expected format: 'remote_$ORDER_$REMOTENAME_$GROUPING or 'virtualfs_$ORDER_$FILESCHEME_$GROUPING.`); diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index c1b3d14da98..a75b4c1198c 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -218,7 +218,7 @@ Registry.as(ConfigurationExtensions.Configuration) 'remote.portsAttributes': { type: 'object', patternProperties: { - '(^\\d+(\\-\\d+)?$)|(.+)': { + '(^\\d+(-\\d+)?$)|(.+)': { type: 'object', description: localize('remote.portsAttributes.port', "A port, range of ports (ex. \"40000-55000\"), host and port (ex. \"db:1234\"), or regular expression (ex. \".+\\\\/server.js\"). For a port number or range, the attributes will apply to that port number or range of port numbers. Attributes which use a regular expression will apply to ports whose associated process command line matches the expression."), properties: { diff --git a/src/vs/workbench/contrib/scm/browser/activity.ts b/src/vs/workbench/contrib/scm/browser/activity.ts index eaf9a8766d0..ce1fb353da4 100644 --- a/src/vs/workbench/contrib/scm/browser/activity.ts +++ b/src/vs/workbench/contrib/scm/browser/activity.ts @@ -10,13 +10,14 @@ import { Event } from 'vs/base/common/event'; import { VIEW_PANE_ID, ISCMService, ISCMRepository, ISCMViewService } from 'vs/workbench/contrib/scm/common/scm'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { EditorResourceAccessor } from 'vs/workbench/common/editor'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { stripIcons } from 'vs/base/common/iconLabels'; +import { Schemas } from 'vs/base/common/network'; function getCount(repository: ISCMRepository): number { if (typeof repository.provider.count === 'number') { @@ -193,3 +194,67 @@ export class SCMStatusController implements IWorkbenchContribution { this.repositoryDisposables.clear(); } } + +export class SCMActiveResourceContextKeyController implements IWorkbenchContribution { + + private contextKey: IContextKey; + private disposables = new DisposableStore(); + private repositoryDisposables = new Set(); + + constructor( + @IContextKeyService readonly contextKeyService: IContextKeyService, + @IEditorService private readonly editorService: IEditorService, + @ISCMService private readonly scmService: ISCMService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService + ) { + this.contextKey = contextKeyService.createKey('scmActiveResourceHasChanges', false); + + this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); + + for (const repository of this.scmService.repositories) { + this.onDidAddRepository(repository); + } + + editorService.onDidActiveEditorChange(this.updateContextKey, this, this.disposables); + } + + private onDidAddRepository(repository: ISCMRepository): void { + const onDidChange = Event.any(repository.provider.onDidChange, repository.provider.onDidChangeResources); + const changeDisposable = onDidChange(() => this.updateContextKey()); + + const onDidRemove = Event.filter(this.scmService.onDidRemoveRepository, e => e === repository); + const removeDisposable = onDidRemove(() => { + disposable.dispose(); + this.repositoryDisposables.delete(disposable); + this.updateContextKey(); + }); + + const disposable = combinedDisposable(changeDisposable, removeDisposable); + this.repositoryDisposables.add(disposable); + } + + private updateContextKey(): void { + const activeResource = EditorResourceAccessor.getOriginalUri(this.editorService.activeEditor); + + if (activeResource && activeResource.scheme === Schemas.file) { + for (const repository of this.scmService.repositories) { + for (const resourceGroup of repository.provider.groups.elements) { + if (resourceGroup.elements.find(scmResource => { + return this.uriIdentityService.extUri.isEqual(activeResource, scmResource.sourceUri); + })) { + this.contextKey.set(true); + return; + } + } + } + } + + this.contextKey.set(false); + } + + dispose(): void { + this.disposables = dispose(this.disposables); + dispose(this.repositoryDisposables.values()); + this.repositoryDisposables.clear(); + } +} diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 8f2cd3f4a7a..1d04a740c71 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -51,6 +51,7 @@ import { gotoNextLocation, gotoPreviousLocation } from 'vs/platform/theme/common import { Codicon } from 'vs/base/common/codicons'; import { onUnexpectedError } from 'vs/base/common/errors'; import { TextCompareEditorActiveContext } from 'vs/workbench/common/editor'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; class DiffActionRunner extends ActionRunner { @@ -1088,7 +1089,8 @@ export class DirtyDiffModel extends Disposable { @ISCMService private readonly scmService: ISCMService, @IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService, @IConfigurationService private readonly configurationService: IConfigurationService, - @ITextModelService private readonly textModelResolverService: ITextModelService + @ITextModelService private readonly textModelResolverService: ITextModelService, + @IProgressService private readonly progressService: IProgressService, ) { super(); this._model = textFileModel; @@ -1160,21 +1162,23 @@ export class DirtyDiffModel extends Disposable { } private diff(): Promise { - return this.getOriginalURIPromise().then(originalURI => { - if (this._disposed || this._model.isDisposed() || !originalURI) { - return Promise.resolve([]); // disposed - } + return this.progressService.withProgress({ location: ProgressLocation.Scm }, async () => { + return this.getOriginalURIPromise().then(originalURI => { + if (this._disposed || this._model.isDisposed() || !originalURI) { + return Promise.resolve([]); // disposed + } - if (!this.editorWorkerService.canComputeDirtyDiff(originalURI, this._model.resource)) { - return Promise.resolve([]); // Files too large - } + if (!this.editorWorkerService.canComputeDirtyDiff(originalURI, this._model.resource)) { + return Promise.resolve([]); // Files too large + } - const ignoreTrimWhitespaceSetting = this.configurationService.getValue<'true' | 'false' | 'inherit'>('scm.diffDecorationsIgnoreTrimWhitespace'); - const ignoreTrimWhitespace = ignoreTrimWhitespaceSetting === 'inherit' - ? this.configurationService.getValue('diffEditor.ignoreTrimWhitespace') - : ignoreTrimWhitespaceSetting !== 'false'; + const ignoreTrimWhitespaceSetting = this.configurationService.getValue<'true' | 'false' | 'inherit'>('scm.diffDecorationsIgnoreTrimWhitespace'); + const ignoreTrimWhitespace = ignoreTrimWhitespaceSetting === 'inherit' + ? this.configurationService.getValue('diffEditor.ignoreTrimWhitespace') + : ignoreTrimWhitespaceSetting !== 'false'; - return this.editorWorkerService.computeDirtyDiff(originalURI, this._model.resource, ignoreTrimWhitespace); + return this.editorWorkerService.computeDirtyDiff(originalURI, this._model.resource, ignoreTrimWhitespace); + }); }); } diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 8728eff160a..a2a5d265b66 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -10,7 +10,7 @@ import { DirtyDiffWorkbenchController } from './dirtydiffDecorator'; import { VIEWLET_ID, ISCMRepository, ISCMService, VIEW_PANE_ID, ISCMProvider, ISCMViewService, REPOSITORIES_VIEW_PANE_ID } from 'vs/workbench/contrib/scm/common/scm'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; -import { SCMStatusController } from './activity'; +import { SCMActiveResourceContextKeyController, SCMStatusController } from './activity'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -97,6 +97,9 @@ viewsRegistry.registerViews([{ containerIcon: sourceControlViewIcon }], viewContainer); +Registry.as(WorkbenchExtensions.Workbench) + .registerWorkbenchContribution(SCMActiveResourceContextKeyController, LifecyclePhase.Restored); + Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(SCMStatusController, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 9e368be9a08..cdc625287a5 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -46,7 +46,7 @@ import { registerContributions as searchWidgetContributions } from 'vs/workbench import { SymbolsQuickAccessProvider } from 'vs/workbench/contrib/search/browser/symbolsQuickAccess'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { resolveResourcesForSearchIncludes } from 'vs/workbench/contrib/search/common/queryBuilder'; -import { getWorkspaceSymbols, SearchStateKey, SearchUIState } from 'vs/workbench/contrib/search/common/search'; +import { getWorkspaceSymbols, IWorkspaceSymbol, SearchStateKey, SearchUIState } from 'vs/workbench/contrib/search/common/search'; import { ISearchHistoryService, SearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; import { FileMatch, FileMatchOrMatch, FolderMatch, ISearchWorkbenchService, Match, RenderableMatch, SearchWorkbenchService } from 'vs/workbench/contrib/search/common/searchModel'; import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants'; @@ -1025,10 +1025,11 @@ configurationRegistry.registerConfiguration({ } }); -CommandsRegistry.registerCommand('_executeWorkspaceSymbolProvider', function (accessor, ...args) { +CommandsRegistry.registerCommand('_executeWorkspaceSymbolProvider', async function (accessor, ...args): Promise { const [query] = args; assertType(typeof query === 'string'); - return getWorkspaceSymbols(query); + const result = await getWorkspaceSymbols(query); + return result.map(item => item.symbol); }); // Go to menu diff --git a/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts index eaa56c70ef2..159ed9e7e72 100644 --- a/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts @@ -119,102 +119,101 @@ export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider 0) { + + // First: try to score on the entire query, it is possible that + // the symbol matches perfectly (e.g. searching for "change log" + // can be a match on a markdown symbol "change log"). In that + // case we want to skip the container query altogether. + if (symbolQuery !== query) { + [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabelWithIcon, { ...query, values: undefined /* disable multi-query support */ }, 0, symbolLabelIconOffset); + if (typeof symbolScore === 'number') { + skipContainerQuery = true; // since we consumed the query, skip any container matching + } + } + + // Otherwise: score on the symbol query and match on the container later + if (typeof symbolScore !== 'number') { + [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabelWithIcon, symbolQuery, 0, symbolLabelIconOffset); + if (typeof symbolScore !== 'number') { + continue; + } + } + } + + const symbolUri = symbol.location.uri; + let containerLabel: string | undefined = undefined; + if (symbolUri) { + const containerPath = this.labelService.getUriLabel(symbolUri, { relative: true }); + if (symbol.containerName) { + containerLabel = `${symbol.containerName} • ${containerPath}`; + } else { + containerLabel = containerPath; + } + } + + // Score by container if specified and searching + let containerScore: number | undefined = undefined; + let containerMatches: IMatch[] | undefined = undefined; + if (!skipContainerQuery && containerQuery && containerQuery.original.length > 0) { + if (containerLabel) { + [containerScore, containerMatches] = scoreFuzzy2(containerLabel, containerQuery); + } + + if (typeof containerScore !== 'number') { continue; } - const symbolLabel = symbol.name; - const symbolLabelWithIcon = `$(symbol-${SymbolKinds.toString(symbol.kind) || 'property'}) ${symbolLabel}`; - const symbolLabelIconOffset = symbolLabelWithIcon.length - symbolLabel.length; - - // Score by symbol label if searching - let symbolScore: number | undefined = undefined; - let symbolMatches: IMatch[] | undefined = undefined; - let skipContainerQuery = false; - if (symbolQuery.original.length > 0) { - - // First: try to score on the entire query, it is possible that - // the symbol matches perfectly (e.g. searching for "change log" - // can be a match on a markdown symbol "change log"). In that - // case we want to skip the container query altogether. - if (symbolQuery !== query) { - [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabelWithIcon, { ...query, values: undefined /* disable multi-query support */ }, 0, symbolLabelIconOffset); - if (typeof symbolScore === 'number') { - skipContainerQuery = true; // since we consumed the query, skip any container matching - } - } - - // Otherwise: score on the symbol query and match on the container later - if (typeof symbolScore !== 'number') { - [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabelWithIcon, symbolQuery, 0, symbolLabelIconOffset); - if (typeof symbolScore !== 'number') { - continue; - } - } + if (typeof symbolScore === 'number') { + symbolScore += containerScore; // boost symbolScore by containerScore } - - const symbolUri = symbol.location.uri; - let containerLabel: string | undefined = undefined; - if (symbolUri) { - const containerPath = this.labelService.getUriLabel(symbolUri, { relative: true }); - if (symbol.containerName) { - containerLabel = `${symbol.containerName} • ${containerPath}`; - } else { - containerLabel = containerPath; - } - } - - // Score by container if specified and searching - let containerScore: number | undefined = undefined; - let containerMatches: IMatch[] | undefined = undefined; - if (!skipContainerQuery && containerQuery && containerQuery.original.length > 0) { - if (containerLabel) { - [containerScore, containerMatches] = scoreFuzzy2(containerLabel, containerQuery); - } - - if (typeof containerScore !== 'number') { - continue; - } - - if (typeof symbolScore === 'number') { - symbolScore += containerScore; // boost symbolScore by containerScore - } - } - - const deprecated = symbol.tags ? symbol.tags.indexOf(SymbolTag.Deprecated) >= 0 : false; - - symbolPicks.push({ - symbol, - resource: symbolUri, - score: symbolScore, - label: symbolLabelWithIcon, - ariaLabel: symbolLabel, - highlights: deprecated ? undefined : { - label: symbolMatches, - description: containerMatches - }, - description: containerLabel, - strikethrough: deprecated, - buttons: [ - { - iconClass: openSideBySideDirection === 'right' ? Codicon.splitHorizontal.classNames : Codicon.splitVertical.classNames, - tooltip: openSideBySideDirection === 'right' ? localize('openToSide', "Open to the Side") : localize('openToBottom', "Open to the Bottom") - } - ], - trigger: (buttonIndex, keyMods) => { - this.openSymbol(provider, symbol, token, { keyMods, forceOpenSideBySide: true }); - - return TriggerAction.CLOSE_PICKER; - }, - accept: async (keyMods, event) => this.openSymbol(provider, symbol, token, { keyMods, preserveFocus: event.inBackground, forcePinned: event.inBackground }), - }); } + + const deprecated = symbol.tags ? symbol.tags.indexOf(SymbolTag.Deprecated) >= 0 : false; + + symbolPicks.push({ + symbol, + resource: symbolUri, + score: symbolScore, + label: symbolLabelWithIcon, + ariaLabel: symbolLabel, + highlights: deprecated ? undefined : { + label: symbolMatches, + description: containerMatches + }, + description: containerLabel, + strikethrough: deprecated, + buttons: [ + { + iconClass: openSideBySideDirection === 'right' ? Codicon.splitHorizontal.classNames : Codicon.splitVertical.classNames, + tooltip: openSideBySideDirection === 'right' ? localize('openToSide', "Open to the Side") : localize('openToBottom', "Open to the Bottom") + } + ], + trigger: (buttonIndex, keyMods) => { + this.openSymbol(provider, symbol, token, { keyMods, forceOpenSideBySide: true }); + + return TriggerAction.CLOSE_PICKER; + }, + accept: async (keyMods, event) => this.openSymbol(provider, symbol, token, { keyMods, preserveFocus: event.inBackground, forcePinned: event.inBackground }), + }); + } // Sort picks (unless disabled) @@ -229,7 +228,7 @@ export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider { +export class WorkspaceSymbolItem { + constructor(readonly symbol: IWorkspaceSymbol, readonly provider: IWorkspaceSymbolProvider) { } +} - const result: [IWorkspaceSymbolProvider, IWorkspaceSymbol[]][] = []; +export async function getWorkspaceSymbols(query: string, token: CancellationToken = CancellationToken.None): Promise { - const promises = WorkspaceSymbolProviderRegistry.all().map(support => { - return Promise.resolve(support.provideWorkspaceSymbols(query, token)).then(value => { - if (Array.isArray(value)) { - result.push([support, value]); + const all: WorkspaceSymbolItem[] = []; + + const promises = WorkspaceSymbolProviderRegistry.all().map(async provider => { + try { + const value = await provider.provideWorkspaceSymbols(query, token); + if (!value) { + return; } - }, onUnexpectedError); + for (let symbol of value) { + all.push(new WorkspaceSymbolItem(symbol, provider)); + } + } catch (err) { + onUnexpectedError(err); + } }); - return Promise.all(promises).then(_ => result); + await Promise.all(promises); + + if (token.isCancellationRequested) { + return []; + } + + // de-duplicate entries + + function compareItems(a: WorkspaceSymbolItem, b: WorkspaceSymbolItem): number { + let res = compare(a.symbol.name, b.symbol.name); + if (res === 0) { + res = a.symbol.kind - b.symbol.kind; + } + if (res === 0) { + res = compare(a.symbol.location.uri.toString(), b.symbol.location.uri.toString()); + } + if (res === 0) { + if (a.symbol.location.range && b.symbol.location.range) { + if (!Range.areIntersecting(a.symbol.location.range, b.symbol.location.range)) { + res = Range.compareRangesUsingStarts(a.symbol.location.range, b.symbol.location.range); + } + } else if (a.provider.resolveWorkspaceSymbol && !b.provider.resolveWorkspaceSymbol) { + res = -1; + } else if (!a.provider.resolveWorkspaceSymbol && b.provider.resolveWorkspaceSymbol) { + res = 1; + } + } + if (res === 0) { + res = compare(a.symbol.containerName ?? '', b.symbol.containerName ?? ''); + } + return res; + } + + return groupBy(all, compareItems).map(group => group[0]).flat(); } export interface IWorkbenchSearchConfigurationProperties extends ISearchConfigurationProperties { diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index add862a216a..2b3f346c6d8 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -771,9 +771,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return this._recentlyUsedTasks; } - private getFolderFromTaskKey(key: string): string | undefined { - const keyValue: { folder: string | undefined } = JSON.parse(key); - return keyValue.folder; + private getFolderFromTaskKey(key: string): { folder: string | undefined, isWorkspaceFile: boolean | undefined } { + const keyValue: { folder: string | undefined, id: string | undefined } = JSON.parse(key); + return { + folder: keyValue.folder, isWorkspaceFile: keyValue.id?.endsWith(TaskSourceKind.WorkspaceFile) + }; } public async readRecentTasks(): Promise<(Task | ConfiguringTask)[]> { @@ -782,40 +784,55 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer folderMap[folder.uri.toString()] = folder; }); const folderToTasksMap: Map = new Map(); + const workspaceToTaskMap: Map = new Map(); const recentlyUsedTasks = this.getRecentlyUsedTasks(); const tasks: (Task | ConfiguringTask)[] = []; + + function addTaskToMap(map: Map, folder: string | undefined, task: any) { + if (folder && !map.has(folder)) { + map.set(folder, []); + } + if (folder && (folderMap[folder] || (folder === USER_TASKS_GROUP_KEY)) && task) { + map.get(folder).push(task); + } + } + for (const entry of recentlyUsedTasks.entries()) { const key = entry[0]; const task = JSON.parse(entry[1]); - const folder = this.getFolderFromTaskKey(key); - if (folder && !folderToTasksMap.has(folder)) { - folderToTasksMap.set(folder, []); - } - if (folder && (folderMap[folder] || (folder === USER_TASKS_GROUP_KEY)) && task) { - folderToTasksMap.get(folder).push(task); - } + const folderInfo = this.getFolderFromTaskKey(key); + addTaskToMap(folderInfo.isWorkspaceFile ? workspaceToTaskMap : folderToTasksMap, folderInfo.folder, task); } + const readTasksMap: Map = new Map(); - for (const key of folderToTasksMap.keys()) { - let custom: CustomTask[] = []; - let customized: IStringDictionary = Object.create(null); - await this.computeTasksForSingleConfig(folderMap[key] ?? await this.getAFolder(), { - version: '2.0.0', - tasks: folderToTasksMap.get(key) - }, TaskRunSource.System, custom, customized, folderMap[key] ? TaskConfig.TaskConfigSource.TasksJson : TaskConfig.TaskConfigSource.User, true); - custom.forEach(task => { - const taskKey = task.getRecentlyUsedKey(); - if (taskKey) { - readTasksMap.set(taskKey, task); - } - }); - for (const configuration in customized) { - const taskKey = customized[configuration].getRecentlyUsedKey(); - if (taskKey) { - readTasksMap.set(taskKey, customized[configuration]); + async function readTasks(that: AbstractTaskService, map: Map, isWorkspaceFile: boolean) { + for (const key of map.keys()) { + let custom: CustomTask[] = []; + let customized: IStringDictionary = Object.create(null); + const taskConfigSource = (folderMap[key] + ? (isWorkspaceFile + ? TaskConfig.TaskConfigSource.WorkspaceFile : TaskConfig.TaskConfigSource.TasksJson) + : TaskConfig.TaskConfigSource.User); + await that.computeTasksForSingleConfig(folderMap[key] ?? await that.getAFolder(), { + version: '2.0.0', + tasks: map.get(key) + }, TaskRunSource.System, custom, customized, taskConfigSource, true); + custom.forEach(task => { + const taskKey = task.getRecentlyUsedKey(); + if (taskKey) { + readTasksMap.set(taskKey, task); + } + }); + for (const configuration in customized) { + const taskKey = customized[configuration].getRecentlyUsedKey(); + if (taskKey) { + readTasksMap.set(taskKey, customized[configuration]); + } } } } + await readTasks(this, folderToTasksMap, false); + await readTasks(this, workspaceToTaskMap, true); for (const key of recentlyUsedTasks.keys()) { if (readTasksMap.has(key)) { @@ -2260,19 +2277,22 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } private async createTaskQuickPickEntries(tasks: Task[], group: boolean = false, sort: boolean = false, selectedEntry?: TaskQuickPickEntry, includeRecents: boolean = true): Promise { - let count: { [key: string]: number; } = {}; + let encounteredTasks: { [key: string]: TaskQuickPickEntry[] } = {}; if (tasks === undefined || tasks === null || tasks.length === 0) { return []; } const TaskQuickPickEntry = (task: Task): TaskQuickPickEntry => { - let entryLabel = task._label; - if (count[task._id]) { - entryLabel = entryLabel + ' (' + count[task._id].toString() + ')'; - count[task._id]++; + const newEntry = { label: task._label, description: this.getTaskDescription(task), task, detail: this.showDetail() ? task.configurationProperties.detail : undefined }; + if (encounteredTasks[task._id]) { + if (encounteredTasks[task._id].length === 1) { + encounteredTasks[task._id][0].label += ' (1)'; + } + newEntry.label = newEntry.label + ' (' + (encounteredTasks[task._id].length + 1).toString() + ')'; } else { - count[task._id] = 1; + encounteredTasks[task._id] = []; } - return { label: entryLabel, description: this.getTaskDescription(task), task, detail: this.showDetail() ? task.configurationProperties.detail : undefined }; + encounteredTasks[task._id].push(newEntry); + return newEntry; }; function fillEntries(entries: QuickPickInput[], tasks: Task[], groupLabel: string): void { @@ -2343,7 +2363,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } entries = tasks.map(task => TaskQuickPickEntry(task)); } - count = {}; + encounteredTasks = {}; return entries; } @@ -2989,7 +3009,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer public getTaskDescription(task: Task | ConfiguringTask): string | undefined { let description: string | undefined; if (task._source.kind === TaskSourceKind.User) { - description = nls.localize('taskQuickPick.userSettings', 'User Settings'); + description = nls.localize('taskQuickPick.userSettings', 'User'); } else if (task._source.kind === TaskSourceKind.WorkspaceFile) { description = task.getWorkspaceFileName(); } else if (this.needsFolderQualification()) { diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 3f12b29d53c..3fd75cfb6db 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -1084,7 +1084,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { let windowsShellArgs: boolean = false; if (platform === Platform.Platform.Windows) { windowsShellArgs = true; - let basename = path.basename(shellLaunchConfig.executable!).toLowerCase(); + let basename = path.posix.basename(URI.file(shellLaunchConfig.executable!).path).toLowerCase(); // If we don't have a cwd, then the terminal uses the home dir. const userHome = await this.pathService.userHome(); if (basename === 'cmd.exe' && ((options.cwd && isUNC(options.cwd)) || (!options.cwd && isUNC(userHome.fsPath)))) { diff --git a/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts b/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts index a3b89c01dc8..8d612f6d9a6 100644 --- a/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts +++ b/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts @@ -40,7 +40,7 @@ const taskDefinitionSchema: IJSONSchema = { }, when: { type: 'string', - markdownDescription: nls.localize('TaskDefinition.when', 'Condition which must be true to enable this type of task. Consider using `shellExecutionSupported`, `processExecutionSupported`, and `customExecutionSupported` as appropriate for this task definition.'), + markdownDescription: nls.localize('TaskDefinition.when', 'Condition which must be true to enable this type of task. Consider using `shellExecutionSupported`, `processExecutionSupported`, and `customExecutionSupported` as appropriate for this task definition. See the [API documentation](https://code.visualstudio.com/api/extension-guides/task-provider#when-clause) for more information.'), default: '' } } diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index bd605002ca4..5710c5ab11a 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -70,6 +70,10 @@ z-index: 31; } +.monaco-workbench .simple-find-part-wrapper { + z-index: 32 !important; +} + .xterm .xterm-screen { cursor: text; } @@ -388,6 +392,7 @@ pointer-events: none; opacity: 0; /* hidden initially */ transition: left 70ms ease-out, right 70ms ease-out, top 70ms ease-out, bottom 70ms ease-out, opacity 150ms ease-out; + z-index: 33; } .monaco-workbench .pane-body.integrated-terminal .terminal-group > .monaco-split-view2.horizontal .terminal-drop-overlay.drop-before { right: 50%; diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 79f80459435..10055403665 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -48,6 +48,7 @@ import { TerminalProfileService } from 'vs/workbench/contrib/terminal/browser/te import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { RemoteTerminalBackendContribution } from 'vs/workbench/contrib/terminal/browser/remoteTerminalBackend'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { TerminalMainContribution } from 'vs/workbench/contrib/terminal/browser/terminalMainContribution'; // Register services registerSingleton(ITerminalService, TerminalService, true); @@ -73,6 +74,7 @@ CommandsRegistry.registerCommand({ id: quickAccessNavigatePreviousInTerminalPick // Register workbench contributions const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchRegistry.registerWorkbenchContribution(TerminalMainContribution, LifecyclePhase.Starting); workbenchRegistry.registerWorkbenchContribution(RemoteTerminalBackendContribution, LifecyclePhase.Starting); // Register configurations @@ -171,8 +173,8 @@ if (isWindows) { primary: KeyMod.CtrlCmd | KeyCode.Backspace, }); } -// Delete word right: alt+d -registerSendSequenceKeybinding('\u000d', { +// Delete word right: alt+d [27, 100] +registerSendSequenceKeybinding('\u001bd', { primary: KeyMod.CtrlCmd | KeyCode.Delete, mac: { primary: KeyMod.Alt | KeyCode.Delete } }); diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 7f2d006ee3f..0a8fe39a7da 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -10,9 +10,6 @@ import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IShellLaunchConfig, ITerminalDimensions, ITerminalLaunchError, ITerminalProfile, ITerminalTabLayoutInfoById, TerminalIcon, TitleEventSource, TerminalShellType, IExtensionTerminalProfile, TerminalLocation, ProcessPropertyType, ProcessCapability, IProcessPropertyMap } from 'vs/platform/terminal/common/terminal'; import { ICommandTracker, INavigationMode, IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalConfigHelper, ITerminalFont, ITerminalBackend, ITerminalProcessExtHostProxy, IRegisterContributedProfileArgs } from 'vs/workbench/contrib/terminal/common/terminal'; -import type { Terminal as XTermTerminal } from 'xterm'; -import type { SearchAddon as XTermSearchAddon } from 'xterm-addon-search'; -import type { Unicode11Addon as XTermUnicode11Addon } from 'xterm-addon-unicode11'; import { ITerminalStatusList } from 'vs/workbench/contrib/terminal/browser/terminalStatusList'; import { Orientation } from 'vs/base/browser/ui/splitview/splitview'; import { IEditableData } from 'vs/workbench/common/views'; @@ -27,38 +24,41 @@ export const ITerminalGroupService = createDecorator('ter export const ITerminalInstanceService = createDecorator('terminalInstanceService'); /** - * A service used by TerminalInstance (and components owned by it) that allows it to break its - * dependency on electron-browser and node layers, while at the same time avoiding a cyclic - * dependency on ITerminalService. + * A service used to create instances or fetch backends, this services allows services that + * ITerminalService depends on to also create instances. * * **This service is intended to only be used within the terminal contrib.** */ export interface ITerminalInstanceService { readonly _serviceBrand: undefined; + /** + * An event that's fired when a terminal instance is created. + */ onDidCreateInstance: Event; - getXtermConstructor(): Promise; - getXtermSearchConstructor(): Promise; - getXtermUnicode11Constructor(): Promise; + /** + * Helper function to convert a shell launch config, a profile or undefined into its equivalent + * shell launch config. + * @param shellLaunchConfigOrProfile A shell launch config, a profile or undefined + * @param cwd A cwd to override. + */ + convertProfileToShellLaunchConfig(shellLaunchConfigOrProfile?: IShellLaunchConfig | ITerminalProfile, cwd?: string | URI): IShellLaunchConfig; /** - * Takes a path and returns the properly escaped path to send to the terminal. - * On Windows, this included trying to prepare the path for WSL if needed. - * - * @param executable The executable off the shellLaunchConfig - * @param title The terminal's title - * @param path The path to be escaped and formatted. - * @param remoteAuthority The remote authority of the terminal's pty. - * @returns An escaped version of the path to be execuded in the terminal. + * Create a new terminal instance. + * @param launchConfig The shell launch config. + * @param target The target of the terminal, when this is undefined the default target will be + * used. + * @param resource The URI for the terminal. Note that this is the unique identifier for the + * terminal, not the cwd. */ - preparePathForTerminalAsync(path: string, executable: string | undefined, title: string, shellType: TerminalShellType, remoteAuthority: string | undefined): Promise; - createInstance(launchConfig: IShellLaunchConfig, target?: TerminalLocation, resource?: URI): ITerminalInstance; /** * Gets the registered backend for a remote authority (undefined = local). This is a convenience * method to avoid using the more verbose fetching from the registry. + * @param remoteAuthority The remote authority of the backend. */ getBackend(remoteAuthority?: string): ITerminalBackend | undefined; } @@ -658,12 +658,24 @@ export interface ITerminalInstance { * process (shell) of the terminal instance. * * @param text The text to send. - * @param addNewLine Whether to add a new line to the text being sent, this is normally - * required to run a command in the terminal. The character(s) added are \n or \r\n - * depending on the platform. This defaults to `true`. + * @param addNewLine Whether to add a new line to the text being sent, this is normally required + * to run a command in the terminal. The character(s) added are \n or \r\n depending on the + * platform. This defaults to `true`. */ sendText(text: string, addNewLine: boolean): Promise; + /** + * Sends a path to the terminal instance, preparing it as needed based on the detected shell + * running within the terminal. The text is written to the stdin of the underlying pty process + * (shell) of the terminal instance. + * + * @param originalPath The path to send. + * @param addNewLine Whether to add a new line to the path being sent, this is normally required + * to run a command in the terminal. The character(s) added are \n or \r\n depending on the + * platform. This defaults to `true`. + */ + sendPath(originalPath: string, addNewLine: boolean): Promise; + /** Scroll the terminal buffer down 1 line. */ scrollDownLine(): void; /** Scroll the terminal buffer down 1 page. */ scrollDownPage(): void; /** Scroll the terminal buffer to the bottom. */ scrollToBottom(): void; @@ -756,9 +768,10 @@ export interface ITerminalInstance { /** * Sets the terminal name to the provided title or triggers a quick pick - * to take user input. + * to take user input. If no title is provided, will reset based to the value indicated + * user's configration. */ - rename(title?: string): Promise; + rename(title?: string | 'triggerQuickpick'): Promise; /** * Triggers a quick pick to change the icon of this terminal. diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index b8e42781552..0ee189630af 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -46,9 +46,11 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { isAbsolute } from 'vs/base/common/path'; +import { ITerminalQuickPickItem } from 'vs/workbench/contrib/terminal/browser/terminalProfileQuickpick'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { getIconId, getColorClass, getUriClasses } from 'vs/workbench/contrib/terminal/browser/terminalIcon'; -// allow-any-unicode-next-line -export const switchTerminalActionViewItemSeparator = '─────────'; +export const switchTerminalActionViewItemSeparator = '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'; export const switchTerminalShowTabsTitle = localize('showTerminalTabs', "Show Tabs"); async function getCwdForSplit(configHelper: ITerminalConfigHelper, instance: ITerminalInstance, folders?: IWorkspaceFolder[], commandService?: ICommandService): Promise { @@ -508,7 +510,6 @@ export function registerTerminalActions() { async run(accessor: ServicesAccessor) { const terminalService = accessor.get(ITerminalService); const terminalGroupService = accessor.get(ITerminalGroupService); - const terminalInstanceService = accessor.get(ITerminalInstanceService); const codeEditorService = accessor.get(ICodeEditorService); const notificationService = accessor.get(INotificationService); const workbenchEnvironmentService = accessor.get(IWorkbenchEnvironmentService); @@ -531,8 +532,7 @@ export function registerTerminalActions() { } // TODO: Convert this to ctrl+c, ctrl+v for pwsh? - const path = await terminalInstanceService.preparePathForTerminalAsync(uri.fsPath, instance.shellLaunchConfig.executable, instance.title, instance.shellType, instance.remoteAuthority); - instance.sendText(path, true); + await instance.sendPath(uri.fsPath, true); return terminalGroupService.showPanel(); } }); @@ -760,7 +760,7 @@ export function registerTerminalActions() { super({ id: TerminalCommandId.ChangeIconPanel, title: terminalStrings.changeIcon, - f1: true, + f1: false, category, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated) }); @@ -802,7 +802,7 @@ export function registerTerminalActions() { super({ id: TerminalCommandId.ChangeColorPanel, title: terminalStrings.changeColor, - f1: true, + f1: false, category, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated) }); @@ -836,7 +836,7 @@ export function registerTerminalActions() { }); } async run(accessor: ServicesAccessor, resource: unknown) { - doWithInstance(accessor, resource)?.rename(); + doWithInstance(accessor, resource)?.rename('triggerQuickpick'); } }); @@ -851,7 +851,7 @@ export function registerTerminalActions() { }); } async run(accessor: ServicesAccessor) { - return accessor.get(ITerminalGroupService).activeInstance?.rename(); + return accessor.get(ITerminalGroupService).activeInstance?.rename('triggerQuickpick'); } }); registerAction2(class extends Action2 { @@ -969,11 +969,13 @@ export function registerTerminalActions() { const labelService = accessor.get(ILabelService); const remoteAgentService = accessor.get(IRemoteAgentService); const notificationService = accessor.get(INotificationService); - const backend = accessor.get(ITerminalInstanceService).getBackend(); const terminalGroupService = accessor.get(ITerminalGroupService); + const remoteAuthority = remoteAgentService.getConnection()?.remoteAuthority ?? undefined; + const backend = accessor.get(ITerminalInstanceService).getBackend(remoteAuthority); + if (!backend) { - throw new Error(`No backend registered for remote authority '${remoteAgentService.getConnection()?.remoteAuthority ?? undefined}'`); + throw new Error(`No backend registered for remote authority '${remoteAuthority}'`); } const terms = await backend.listProcesses(); @@ -985,8 +987,7 @@ export function registerTerminalActions() { const cwdLabel = labelService.getUriLabel(URI.file(term.cwd)); return { label: term.title, - // allow-any-unicode-next-line - detail: term.workspaceName ? `${term.workspaceName} ⸱ ${cwdLabel}` : cwdLabel, + detail: term.workspaceName ? `${term.workspaceName} \u2E31 ${cwdLabel}` : cwdLabel, description: term.pid ? String(term.pid) : '', term }; @@ -1582,6 +1583,52 @@ export function registerTerminalActions() { } } }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TerminalCommandId.Join, + title: { value: localize('workbench.action.terminal.join', "Join Terminals"), original: 'Join Terminals' }, + category, + f1: true, + precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated)) + }); + } + async run(accessor: ServicesAccessor) { + const themeService = accessor.get(IThemeService); + const groupService = accessor.get(ITerminalGroupService); + const picks: ITerminalQuickPickItem[] = []; + if (!groupService.activeInstance || groupService.instances.length === 1) { + return; + } + const otherInstances = groupService.instances.filter(i => i.instanceId !== groupService.activeInstance?.instanceId); + for (const terminal of otherInstances) { + const group = groupService.getGroupForInstance(terminal); + if (group?.terminalInstances.length === 1) { + const iconId = getIconId(terminal); + const label = `$(${iconId}): ${terminal.title}`; + const iconClasses: string[] = []; + const colorClass = getColorClass(terminal); + if (colorClass) { + iconClasses.push(colorClass); + } + const uriClasses = getUriClasses(terminal, themeService.getColorTheme().type); + if (uriClasses) { + iconClasses.push(...uriClasses); + } + picks.push({ + terminal, + label, + iconClasses + }); + } + } + const result = await accessor.get(IQuickInputService).pick(picks, {}); + if (result) { + groupService.joinInstances([result.terminal, groupService.activeInstance!]); + } + } + } + ); registerAction2(class extends Action2 { constructor() { super({ @@ -1736,6 +1783,24 @@ export function registerTerminalActions() { } } }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TerminalCommandId.KillAll, + title: { value: localize('workbench.action.terminal.killAll', "Kill All Terminals"), original: 'Kill All Terminals' }, + f1: true, + category, + precondition: ContextKeyExpr.or(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.isOpen), + icon: Codicon.trash + }); + } + async run(accessor: ServicesAccessor) { + const terminalService = accessor.get(ITerminalService); + for (const instance of terminalService.instances) { + await terminalService.safeDisposeTerminal(instance); + } + } + }); registerAction2(class extends Action2 { constructor() { super({ @@ -1818,7 +1883,7 @@ export function registerTerminalActions() { constructor() { super({ id: TerminalCommandId.SelectDefaultProfile, - title: { value: localize('workbench.action.terminal.selectDefaultProfile', "Select Default Profile"), original: 'Select Default Profile' }, + title: { value: localize('workbench.action.terminal.selectDefaultShell', "Select Default Profile"), original: 'Select Default Profile' }, f1: true, category, precondition: TerminalContextKeys.processSupported @@ -2067,8 +2132,8 @@ function focusNext(accessor: ServicesAccessor): void { export function validateTerminalName(name: string): { content: string, severity: Severity } | null { if (!name || name.trim().length === 0) { return { - content: localize('emptyTerminalNameError', "A name must be provided."), - severity: Severity.Error + content: localize('emptyTerminalNameInfo', "Providing no name will reset it to the default value"), + severity: Severity.Info }; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts index 4f56bbebdee..30da8845d02 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts @@ -20,7 +20,6 @@ import { getInstanceFromResource } from 'vs/workbench/contrib/terminal/browser/t import { TerminalViewPane } from 'vs/workbench/contrib/terminal/browser/terminalView'; import { TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; export class TerminalGroupService extends Disposable implements ITerminalGroupService, ITerminalFindHost { declare _serviceBrand: undefined; @@ -60,7 +59,6 @@ export class TerminalGroupService extends Disposable implements ITerminalGroupSe @IContextKeyService private _contextKeyService: IContextKeyService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IViewsService private readonly _viewsService: IViewsService, - @IWorkbenchLayoutService private _layoutService: IWorkbenchLayoutService, @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService, @IConfigurationService private readonly _configurationService: IConfigurationService, ) { @@ -81,7 +79,7 @@ export class TerminalGroupService extends Disposable implements ITerminalGroupSe if (location === ViewContainerLocation.Panel) { const panel = this._viewDescriptorService.getViewContainerByViewId(TERMINAL_VIEW_ID); if (panel && this._viewDescriptorService.getViewContainerModel(panel).activeViewDescriptors.length === 1) { - this._layoutService.setPartHidden(true, Parts.PANEL_PART); + this._viewsService.closeView(TERMINAL_VIEW_ID); TerminalContextKeys.tabsMouse.bindTo(this._contextKeyService).set(false); } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 08ffd2a1e3a..712e0057c7a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -23,11 +23,11 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry'; import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; -import { ITerminalProcessManager, ProcessState, TERMINAL_VIEW_ID, INavigationMode, DEFAULT_COMMANDS_TO_SKIP_SHELL, TERMINAL_CREATION_COMMANDS, ITerminalProfileResolverService, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalProcessManager, ProcessState, TERMINAL_VIEW_ID, INavigationMode, DEFAULT_COMMANDS_TO_SKIP_SHELL, TERMINAL_CREATION_COMMANDS, ITerminalProfileResolverService, TerminalCommandId, ITerminalBackend } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { TerminalLinkManager } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { ITerminalInstanceService, ITerminalInstance, ITerminalExternalLinkProvider, IRequestAddInstanceToGroupEvent } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalInstance, ITerminalExternalLinkProvider, IRequestAddInstanceToGroupEvent } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager'; import type { Terminal as XTermTerminal, ITerminalAddon } from 'xterm'; import { NavigationModeAddon } from 'vs/workbench/contrib/terminal/browser/xterm/navigationModeAddon'; @@ -38,7 +38,7 @@ import { TypeAheadAddon } from 'vs/workbench/contrib/terminal/browser/terminalTy import { BrowserFeatures } from 'vs/base/browser/canIUse'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable'; -import { IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalShellType, TerminalSettingId, TitleEventSource, TerminalIcon, TerminalLocation, ProcessPropertyType, ProcessCapability, IProcessPropertyMap } from 'vs/platform/terminal/common/terminal'; +import { IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalShellType, TerminalSettingId, TitleEventSource, TerminalIcon, TerminalLocation, ProcessPropertyType, ProcessCapability, IProcessPropertyMap, WindowsShellType } from 'vs/platform/terminal/common/terminal'; import { IProductService } from 'vs/platform/product/common/productService'; import { formatMessageForTerminal } from 'vs/workbench/contrib/terminal/common/terminalStrings'; import { AutoOpenBarrier, Promises } from 'vs/base/common/async'; @@ -64,6 +64,7 @@ import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableEle import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { LineDataEventAddon } from 'vs/workbench/contrib/terminal/browser/xterm/lineDataEventAddon'; import { XtermTerminal } from 'vs/workbench/contrib/terminal/browser/xterm/xtermTerminal'; +import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnvironment'; const enum Constants { /** @@ -80,6 +81,19 @@ const enum Constants { } let xtermConstructor: Promise | undefined; +function getXtermConstructor(): Promise { + if (xtermConstructor) { + return xtermConstructor; + } + xtermConstructor = Promises.withAsyncBody(async (resolve) => { + const Terminal = (await import('xterm')).Terminal; + // Localize strings + Terminal.strings.promptLabel = nls.localize('terminal.integrated.a11yPromptLabel', 'Terminal input'); + Terminal.strings.tooMuchOutput = nls.localize('terminal.integrated.a11yTooMuchOutput', 'Too much output to announce, navigate to rows manually to read'); + resolve(Terminal); + }); + return xtermConstructor; +} interface ICanvasDimensions { width: number; @@ -282,7 +296,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private readonly _configHelper: TerminalConfigHelper, private _shellLaunchConfig: IShellLaunchConfig, resource: URI | undefined, - @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService, @ITerminalProfileResolverService private readonly _terminalProfileResolverService: ITerminalProfileResolverService, @IPathService private readonly _pathService: IPathService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @@ -537,25 +550,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { get persistentProcessId(): number | undefined { return this._processManager.persistentProcessId; } get shouldPersist(): boolean { return this._processManager.shouldPersist; } - private async _getXtermConstructor(): Promise { - if (xtermConstructor) { - return xtermConstructor; - } - xtermConstructor = Promises.withAsyncBody(async (resolve) => { - const Terminal = await this._terminalInstanceService.getXtermConstructor(); - // Localize strings - Terminal.strings.promptLabel = nls.localize('terminal.integrated.a11yPromptLabel', 'Terminal input'); - Terminal.strings.tooMuchOutput = nls.localize('terminal.integrated.a11yTooMuchOutput', 'Too much output to announce, navigate to rows manually to read'); - resolve(Terminal); - }); - return xtermConstructor; - } - /** * Create xterm.js instance and attach data listeners. */ protected async _createXterm(): Promise { - const Terminal = await this._getXtermConstructor(); + const Terminal = await getXtermConstructor(); if (this._isDisposed) { throw new Error('Terminal disposed of during xterm.js creation'); } @@ -622,15 +621,35 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._onLinksReady.fire(this); }); - // TODO: This should be an optional addon - this._xtermTypeAheadAddon = this._register(this._instantiationService.createInstance(TypeAheadAddon, this._processManager, this._configHelper)); - xterm.raw.loadAddon(this._xtermTypeAheadAddon); + this._loadTypeAheadAddon(xterm); + + this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(TerminalSettingId.LocalEchoEnabled)) { + this._loadTypeAheadAddon(xterm); + } + }); + this._pathService.userHome().then(userHome => { this._userHome = userHome.fsPath; }); return xterm; } + private _loadTypeAheadAddon(xterm: XtermTerminal): void { + const enabled = this._configHelper.config.localEchoEnabled; + const isRemote = !!this.remoteAuthority; + if (enabled === 'off' || enabled === 'auto' && !isRemote) { + return this._xtermTypeAheadAddon?.dispose(); + } + if (this._xtermTypeAheadAddon) { + return; + } + if (enabled === 'on' || (enabled === 'auto' && isRemote)) { + this._xtermTypeAheadAddon = this._register(this._instantiationService.createInstance(TypeAheadAddon, this._processManager, this._configHelper)); + xterm.raw.loadAddon(this._xtermTypeAheadAddon); + } + } + detachFromElement(): void { this._wrapperElement?.remove(); this._container = undefined; @@ -832,9 +851,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const dndController = this._instantiationService.createInstance(TerminalInstanceDragAndDropController, container); dndController.onDropTerminal(e => this._onRequestAddInstanceToGroup.fire(e)); dndController.onDropFile(async path => { - const preparedPath = await this._terminalInstanceService.preparePathForTerminalAsync(path, this.shellLaunchConfig.executable, this.title, this.shellType, this._processManager.remoteAuthority); - this.sendText(preparedPath, false); this.focus(); + await this.sendPath(path, false); }); this._dndObserver = new DragAndDropObserver(container, dndController); } @@ -916,7 +934,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } async detachFromProcess(): Promise { + // Detach the process and dispose the instance, without the instance dispose the terminal + // won't go away await this._processManager.detachFromProcess(); + this.dispose(); } focus(force?: boolean): void { @@ -968,6 +989,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._onDidInputData.fire(this); } + async sendPath(originalPath: string, addNewLine: boolean): Promise { + const preparedPath = await preparePathForShell(originalPath, this.shellLaunchConfig.executable, this.title, this.shellType, this._processManager.backend); + return this.sendText(preparedPath, addNewLine); + } + setVisible(visible: boolean): void { this._isVisible = visible; if (this._wrapperElement) { @@ -1163,64 +1189,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { await this._flushXtermData(); this._logService.debug(`Terminal process exit (instanceId: ${this.instanceId}) with code ${this._exitCode}`); - let exitCodeMessage: string | undefined; - - // Create exit code message - switch (typeof exitCodeOrError) { - case 'number': - // Only show the error if the exit code is non-zero - this._exitCode = exitCodeOrError; - if (this._exitCode === 0) { - break; - } - - let commandLine: string | undefined = undefined; - if (this._shellLaunchConfig.executable) { - commandLine = this._shellLaunchConfig.executable; - if (typeof this._shellLaunchConfig.args === 'string') { - commandLine += ` ${this._shellLaunchConfig.args}`; - } else if (this._shellLaunchConfig.args && this._shellLaunchConfig.args.length) { - commandLine += this._shellLaunchConfig.args.map(a => ` '${a}'`).join(); - } - } - - if (this._processManager.processState === ProcessState.KilledDuringLaunch) { - if (commandLine) { - exitCodeMessage = nls.localize('launchFailed.exitCodeAndCommandLine', "The terminal process \"{0}\" failed to launch (exit code: {1}).", commandLine, this._exitCode); - break; - } - exitCodeMessage = nls.localize('launchFailed.exitCodeOnly', "The terminal process failed to launch (exit code: {0}).", this._exitCode); - break; - } - if (commandLine) { - exitCodeMessage = nls.localize('terminated.exitCodeAndCommandLine', "The terminal process \"{0}\" terminated with exit code: {1}.", commandLine, this._exitCode); - break; - } - exitCodeMessage = nls.localize('terminated.exitCodeOnly', "The terminal process terminated with exit code: {0}.", this._exitCode); - break; - case 'object': - if (exitCodeOrError.message.toString().includes('Could not find pty with id')) { - break; - } - this._exitCode = exitCodeOrError.code; - const conptyError = exitCodeOrError.message.match(/.*error code:\s*(\d+).*$/); - if (conptyError) { - const errorCode = conptyError.length > 1 ? parseInt(conptyError[1]) : undefined; - switch (errorCode) { - case 5: - exitCodeOrError.message = `Access was denied to the path containing your executable ${this.shellLaunchConfig.executable}. Manage and change your permissions to get this to work.`; - break; - case 267: - exitCodeOrError.message = `Invalid starting directory ${this.initialCwd}, review your terminal.integrated.cwd setting`; - break; - case 1260: - exitCodeOrError.message = `Windows cannot open this program because it has been prevented by a software restriction policy. For more information, open Event Viewer or contact your system Administrator`; - break; - } - } - exitCodeMessage = nls.localize('launchFailed.errorMessage', "The terminal process failed to launch: {0}.", exitCodeOrError.message); - break; - } + const parsedExitResult = parseExitResult(exitCodeOrError, this.shellLaunchConfig, this._processManager.processState, this._initialCwd); + this._exitCode = parsedExitResult?.code; + const exitMessage = parsedExitResult?.message; this._logService.debug(`Terminal process exit (instanceId: ${this.instanceId}) state ${this._processManager.processState}`); @@ -1228,8 +1199,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // user (via the `workbench.action.terminal.kill` command). if (this._shellLaunchConfig.waitOnExit && this._processManager.processState !== ProcessState.KilledByUser) { this._xtermReadyPromise.then(xterm => { - if (exitCodeMessage) { - xterm.raw.writeln(exitCodeMessage); + if (exitMessage) { + xterm.raw.writeln(exitMessage); } if (typeof this._shellLaunchConfig.waitOnExit === 'string') { xterm.raw.write(formatMessageForTerminal(this._shellLaunchConfig.waitOnExit)); @@ -1242,19 +1213,19 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { }); } else { this.dispose(); - if (exitCodeMessage) { + if (exitMessage) { const failedDuringLaunch = this._processManager.processState === ProcessState.KilledDuringLaunch; if (failedDuringLaunch || this._configHelper.config.showExitAlert) { // Always show launch failures this._notificationService.notify({ - message: exitCodeMessage, + message: exitMessage, severity: Severity.Error, actions: { primary: [this._instantiationService.createInstance(TerminalLaunchHelpAction)] } }); } else { // Log to help surface the error in case users report issues with showExitAlert // disabled - this._logService.warn(exitCodeMessage); + this._logService.warn(exitMessage); } } } @@ -1515,10 +1486,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } refreshTabLabels(title: string | undefined, eventSource: TitleEventSource): void { + const reset = !title; title = this._updateTitleProperties(title, eventSource); const titleChanged = title !== this._title; this._title = title; - this._labelComputer?.refreshLabel(); + this._labelComputer?.refreshLabel(reset); this._setAriaLabel(this.xterm?.raw, this._instanceId, this._title); if (this._titleReadyComplete) { @@ -1811,16 +1783,14 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return this._linkManager.registerExternalLinkProvider(this, provider); } - async rename(title?: string) { - if (!title) { + async rename(title?: string | 'triggerQuickpick') { + if (title === 'triggerQuickpick') { title = await this._quickInputService.input({ value: this.title, prompt: nls.localize('workbench.action.terminal.rename.prompt', "Enter terminal name"), }); } - if (title) { - this.refreshTabLabels(title, TitleEventSource.Api); - } + this.refreshTabLabels(title, TitleEventSource.Api); } async changeIcon() { @@ -2088,17 +2058,18 @@ export class TerminalLabelComputer extends Disposable { super(); } - refreshLabel(): void { - this._title = this.computeLabel(this._configHelper.config.tabs.title, TerminalLabelType.Title); + refreshLabel(reset?: boolean): void { + this._title = this.computeLabel(this._configHelper.config.tabs.title, TerminalLabelType.Title, reset); this._description = this.computeLabel(this._configHelper.config.tabs.description, TerminalLabelType.Description); - if (this._title !== this._instance.title || this._description !== this._instance.description) { + if (this._title !== this._instance.title || this._description !== this._instance.description || reset) { this._onDidChangeLabel.fire({ title: this._title, description: this._description }); } } computeLabel( labelTemplate: string, - labelType: TerminalLabelType + labelType: TerminalLabelType, + reset?: boolean ) { const templateProperties: ITerminalLabelTemplateProperties = { cwd: this._instance.cwd || this._instance.initialCwd || '', @@ -2117,7 +2088,7 @@ export class TerminalLabelComputer extends Disposable { if (!labelTemplate) { return labelType === TerminalLabelType.Title ? (this._instance.processName || '') : ''; } - if (this._instance.staticTitle && labelType === TerminalLabelType.Title) { + if (!reset && this._instance.staticTitle && labelType === TerminalLabelType.Title) { return this._instance.staticTitle.replace(/[\n\r\t]/g, '') || templateProperties.process?.replace(/[\n\r\t]/g, '') || ''; } const detection = this._instance.capabilities.includes(ProcessCapability.CwdDetection); @@ -2151,3 +2122,145 @@ export class TerminalLabelComputer extends Disposable { return true; } } + +export function parseExitResult( + exitCodeOrError: ITerminalLaunchError | number | undefined, + shellLaunchConfig: IShellLaunchConfig, + processState: ProcessState, + initialCwd: string | undefined +): { code: number | undefined, message: string | undefined } | undefined { + // Only return a message if the exit code is non-zero + if (exitCodeOrError === undefined || exitCodeOrError === 0) { + return { code: exitCodeOrError, message: undefined }; + } + + const code = typeof exitCodeOrError === 'number' ? exitCodeOrError : exitCodeOrError.code; + + // Create exit code message + let message: string | undefined = undefined; + switch (typeof exitCodeOrError) { + case 'number': + let commandLine: string | undefined = undefined; + if (shellLaunchConfig.executable) { + commandLine = shellLaunchConfig.executable; + if (typeof shellLaunchConfig.args === 'string') { + commandLine += ` ${shellLaunchConfig.args}`; + } else if (shellLaunchConfig.args && shellLaunchConfig.args.length) { + commandLine += shellLaunchConfig.args.map(a => ` '${a}'`).join(); + } + } + + if (processState === ProcessState.KilledDuringLaunch) { + if (commandLine) { + message = nls.localize('launchFailed.exitCodeAndCommandLine', "The terminal process \"{0}\" failed to launch (exit code: {1}).", commandLine, code); + } else { + message = nls.localize('launchFailed.exitCodeOnly', "The terminal process failed to launch (exit code: {0}).", code); + } + } else { + if (commandLine) { + message = nls.localize('terminated.exitCodeAndCommandLine', "The terminal process \"{0}\" terminated with exit code: {1}.", commandLine, code); + } else { + message = nls.localize('terminated.exitCodeOnly', "The terminal process terminated with exit code: {0}.", code); + } + } + break; + case 'object': + // Ignore internal errors + if (exitCodeOrError.message.toString().includes('Could not find pty with id')) { + break; + } + // Convert conpty code-based failures into human friendly messages + let innerMessage = exitCodeOrError.message; + const conptyError = exitCodeOrError.message.match(/.*error code:\s*(\d+).*$/); + if (conptyError) { + const errorCode = conptyError.length > 1 ? parseInt(conptyError[1]) : undefined; + switch (errorCode) { + case 5: + innerMessage = `Access was denied to the path containing your executable "${shellLaunchConfig.executable}". Manage and change your permissions to get this to work`; + break; + case 267: + innerMessage = `Invalid starting directory "${initialCwd}", review your terminal.integrated.cwd setting`; + break; + case 1260: + innerMessage = `Windows cannot open this program because it has been prevented by a software restriction policy. For more information, open Event Viewer or contact your system Administrator`; + break; + } + } + message = nls.localize('launchFailed.errorMessage', "The terminal process failed to launch: {0}.", innerMessage); + break; + } + + return { code, message }; +} + +/** + * Takes a path and returns the properly escaped path to send to a given shell. On Windows, this + * included trying to prepare the path for WSL if needed. + * + * @param originalPath The path to be escaped and formatted. + * @param executable The executable off the shellLaunchConfig. + * @param title The terminal's title. + * @param shellType The type of shell the path is being sent to. + * @param backend The backend for the terminal. + * @returns An escaped version of the path to be execuded in the terminal. + */ +async function preparePathForShell(originalPath: string, executable: string | undefined, title: string, shellType: TerminalShellType, backend: ITerminalBackend | undefined): Promise { + return new Promise(c => { + if (!executable) { + c(originalPath); + return; + } + + const hasSpace = originalPath.indexOf(' ') !== -1; + const hasParens = originalPath.indexOf('(') !== -1 || originalPath.indexOf(')') !== -1; + + const pathBasename = path.basename(executable, '.exe'); + const isPowerShell = pathBasename === 'pwsh' || + title === 'pwsh' || + pathBasename === 'powershell' || + title === 'powershell'; + + if (isPowerShell && (hasSpace || originalPath.indexOf('\'') !== -1)) { + c(`& '${originalPath.replace(/'/g, '\'\'')}'`); + return; + } + + if (hasParens && isPowerShell) { + c(`& '${originalPath}'`); + return; + } + + // TODO: This should use the process manager's OS, not the local OS + if (isWindows) { + // 17063 is the build number where wsl path was introduced. + // Update Windows uriPath to be executed in WSL. + if (shellType !== undefined) { + if (shellType === WindowsShellType.GitBash) { + c(originalPath.replace(/\\/g, '/')); + } + else if (shellType === WindowsShellType.Wsl) { + c(backend?.getWslPath(originalPath) || originalPath); + } + + else if (hasSpace) { + c('"' + originalPath + '"'); + } else { + c(originalPath); + } + } else { + const lowerExecutable = executable.toLowerCase(); + if (lowerExecutable.indexOf('wsl') !== -1 || (lowerExecutable.indexOf('bash.exe') !== -1 && lowerExecutable.toLowerCase().indexOf('git') === -1)) { + c(backend?.getWslPath(originalPath) || originalPath); + } else if (hasSpace) { + c('"' + originalPath + '"'); + } else { + c(originalPath); + } + } + + return; + } + + c(escapeNonWindowsPath(originalPath)); + }); +} diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts index d1b825d0778..c9da9557b27 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts @@ -4,16 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { ITerminalInstance, ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; -import type { Terminal as XTermTerminal } from 'xterm'; -import type { SearchAddon as XTermSearchAddon } from 'xterm-addon-search'; -import type { Unicode11Addon as XTermUnicode11Addon } from 'xterm-addon-unicode11'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IShellLaunchConfig, ITerminalProfile, TerminalLocation, TerminalShellType, WindowsShellType } from 'vs/platform/terminal/common/terminal'; +import { IShellLaunchConfig, ITerminalProfile, TerminalLocation } from 'vs/platform/terminal/common/terminal'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnvironment'; -import { basename } from 'vs/base/common/path'; -import { isWindows } from 'vs/base/common/platform'; import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; @@ -23,10 +17,6 @@ import { Emitter, Event } from 'vs/base/common/event'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { Registry } from 'vs/platform/registry/common/platform'; -let Terminal: typeof XTermTerminal; -let SearchAddon: typeof XTermSearchAddon; -let Unicode11Addon: typeof XTermUnicode11Addon; - export class TerminalInstanceService extends Disposable implements ITerminalInstanceService { declare _serviceBrand: undefined; private _terminalFocusContextKey: IContextKey; @@ -53,7 +43,7 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst createInstance(profile: ITerminalProfile, target?: TerminalLocation, resource?: URI): ITerminalInstance; createInstance(shellLaunchConfig: IShellLaunchConfig, target?: TerminalLocation, resource?: URI): ITerminalInstance; createInstance(config: IShellLaunchConfig | ITerminalProfile, target?: TerminalLocation, resource?: URI): ITerminalInstance { - const shellLaunchConfig = this._convertProfileToShellLaunchConfig(config); + const shellLaunchConfig = this.convertProfileToShellLaunchConfig(config); const instance = this._instantiationService.createInstance(TerminalInstance, this._terminalFocusContextKey, this._terminalHasFixedWidth, @@ -68,10 +58,13 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst return instance; } - private _convertProfileToShellLaunchConfig(shellLaunchConfigOrProfile?: IShellLaunchConfig | ITerminalProfile, cwd?: string | URI): IShellLaunchConfig { + convertProfileToShellLaunchConfig(shellLaunchConfigOrProfile?: IShellLaunchConfig | ITerminalProfile, cwd?: string | URI): IShellLaunchConfig { // Profile was provided if (shellLaunchConfigOrProfile && 'profileName' in shellLaunchConfigOrProfile) { const profile = shellLaunchConfigOrProfile; + if (!profile.path) { + return shellLaunchConfigOrProfile; + } return { executable: profile.path, args: profile.args, @@ -83,7 +76,7 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst }; } - // Shell launch config was provided + // A shell launch config was provided if (shellLaunchConfigOrProfile) { if (cwd) { shellLaunchConfigOrProfile.cwd = cwd; @@ -95,89 +88,6 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst return {}; } - async getXtermConstructor(): Promise { - if (!Terminal) { - Terminal = (await import('xterm')).Terminal; - } - return Terminal; - } - - async getXtermSearchConstructor(): Promise { - if (!SearchAddon) { - SearchAddon = (await import('xterm-addon-search')).SearchAddon; - } - return SearchAddon; - } - - async getXtermUnicode11Constructor(): Promise { - if (!Unicode11Addon) { - Unicode11Addon = (await import('xterm-addon-unicode11')).Unicode11Addon; - } - return Unicode11Addon; - } - - async preparePathForTerminalAsync(originalPath: string, executable: string | undefined, title: string, shellType: TerminalShellType, remoteAuthority: string | undefined): Promise { - return new Promise(c => { - if (!executable) { - c(originalPath); - return; - } - - const hasSpace = originalPath.indexOf(' ') !== -1; - const hasParens = originalPath.indexOf('(') !== -1 || originalPath.indexOf(')') !== -1; - - const pathBasename = basename(executable, '.exe'); - const isPowerShell = pathBasename === 'pwsh' || - title === 'pwsh' || - pathBasename === 'powershell' || - title === 'powershell'; - - if (isPowerShell && (hasSpace || originalPath.indexOf('\'') !== -1)) { - c(`& '${originalPath.replace(/'/g, '\'\'')}'`); - return; - } - - if (hasParens && isPowerShell) { - c(`& '${originalPath}'`); - return; - } - - if (isWindows) { - // 17063 is the build number where wsl path was introduced. - // Update Windows uriPath to be executed in WSL. - if (shellType !== undefined) { - if (shellType === WindowsShellType.GitBash) { - c(originalPath.replace(/\\/g, '/')); - } - else if (shellType === WindowsShellType.Wsl) { - const offProcService = this.getBackend(remoteAuthority); - c(offProcService?.getWslPath(originalPath) || originalPath); - } - - else if (hasSpace) { - c('"' + originalPath + '"'); - } else { - c(originalPath); - } - } else { - const lowerExecutable = executable.toLowerCase(); - if (lowerExecutable.indexOf('wsl') !== -1 || (lowerExecutable.indexOf('bash.exe') !== -1 && lowerExecutable.toLowerCase().indexOf('git') === -1)) { - const offProcService = this.getBackend(remoteAuthority); - c(offProcService?.getWslPath(originalPath) || originalPath); - } else if (hasSpace) { - c('"' + originalPath + '"'); - } else { - c(originalPath); - } - } - - return; - } - - c(escapeNonWindowsPath(originalPath)); - }); - } - getBackend(remoteAuthority?: string): ITerminalBackend | undefined { return Registry.as(TerminalExtensions.Backend).getTerminalBackend(remoteAuthority); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalMainContribution.ts b/src/vs/workbench/contrib/terminal/browser/terminalMainContribution.ts new file mode 100644 index 00000000000..d58f8bf21ba --- /dev/null +++ b/src/vs/workbench/contrib/terminal/browser/terminalMainContribution.ts @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Schemas } from 'vs/base/common/network'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { ITerminalEditorService, ITerminalGroupService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { TerminalEditor } from 'vs/workbench/contrib/terminal/browser/terminalEditor'; +import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings'; +import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; + +/** + * The main contribution for the terminal contrib. This contains calls to other components necessary + * to set up the terminal but don't need to be tracked in the long term (where TerminalService would + * be more relevant). + */ +export class TerminalMainContribution implements IWorkbenchContribution { + constructor( + @IEditorResolverService editorResolverService: IEditorResolverService, + @ILabelService labelService: ILabelService, + @ITerminalService terminalService: ITerminalService, + @ITerminalEditorService terminalEditorService: ITerminalEditorService, + @ITerminalGroupService terminalGroupService: ITerminalGroupService + ) { + // Register terminal editors + editorResolverService.registerEditor( + `${Schemas.vscodeTerminal}:/**`, + { + id: TerminalEditor.ID, + label: terminalStrings.terminal, + priority: RegisteredEditorPriority.exclusive + }, + { + canHandleDiff: false, + canSupportResource: uri => uri.scheme === Schemas.vscodeTerminal, + singlePerResource: true + }, + ({ resource, options }) => { + let instance = terminalService.getInstanceFromResource(resource); + if (instance) { + const sourceGroup = terminalGroupService.getGroupForInstance(instance); + if (sourceGroup) { + sourceGroup.removeInstance(instance); + } + } + const resolvedResource = terminalEditorService.resolveResource(instance || resource); + const editor = terminalEditorService.getInputFromResource(resolvedResource) || { editor: resolvedResource }; + return { + editor, + options: { + ...options, + pinned: true, + forceReload: true, + override: TerminalEditor.ID + } + }; + } + ); + + // Register a resource formatter for terminal URIs + labelService.registerFormatter({ + scheme: Schemas.vscodeTerminal, + formatting: { + label: '${path}', + separator: '' + } + }); + } +} diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index a63d043a0b8..9063d049942 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -59,6 +59,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce userHome: string | undefined; isDisconnected: boolean = false; environmentVariableInfo: IEnvironmentVariableInfo | undefined; + backend: ITerminalBackend | undefined; private _isDisposed: boolean = false; private _process: ITerminalChildProcess | null = null; @@ -167,6 +168,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce async detachFromProcess(): Promise { await this._process?.detach?.(); + this._process = null; } async createProcess( @@ -192,11 +194,11 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce } else { this.remoteAuthority = this._workbenchEnvironmentService.remoteAuthority; } - const backend = this._terminalInstanceService.getBackend(this.remoteAuthority); if (!backend) { throw new Error(`No terminal backend registered for remote authority '${this.remoteAuthority}'`); } + this.backend = backend; // Create variable resolver const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts index e7dac04a1c1..1f2efc11842 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts @@ -12,7 +12,8 @@ import { configureTerminalProfileIcon } from 'vs/workbench/contrib/terminal/brow import * as nls from 'vs/nls'; import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal'; -import { IQuickPickTerminalObject } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { IQuickPickTerminalObject, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess'; type DefaultProfileName = string; @@ -238,3 +239,7 @@ export interface IProfileQuickPickItem extends IQuickPickItem { profileName: string; keyMods?: IKeyMods | undefined; } + +export interface ITerminalQuickPickItem extends IPickerQuickAccessItem { + terminal: ITerminalInstance +} diff --git a/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts b/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts index 4344e2f0a19..873f4e0ea91 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts @@ -76,9 +76,7 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider this._commandService.executeCommand(TerminalCommandId.NewWithProfile) }); - return terminalPicks; - } private _createPick(terminal: ITerminalInstance, terminalIndex: number, filter: string, groupIndex?: number): IPickerQuickAccessItem | undefined { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index ef444259b8a..8f64411583d 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -17,9 +17,8 @@ import * as nls from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ILabelService } from 'vs/platform/label/common/label'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { ICreateContributedTerminalProfileOptions, IShellLaunchConfig, ITerminalLaunchError, ITerminalProfile, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalLocation, TerminalLocationString } from 'vs/platform/terminal/common/terminal'; +import { ICreateContributedTerminalProfileOptions, IShellLaunchConfig, ITerminalLaunchError, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalLocation, TerminalLocationString } from 'vs/platform/terminal/common/terminal'; import { iconForeground } from 'vs/platform/theme/common/colorRegistry'; import { IconDefinition } from 'vs/platform/theme/common/iconRegistry'; import { ColorScheme } from 'vs/platform/theme/common/theme'; @@ -28,14 +27,12 @@ import { VirtualWorkspaceContext } from 'vs/workbench/browser/contextkeys'; import { IEditableData, IViewsService } from 'vs/workbench/common/views'; import { ICreateTerminalOptions, IRequestAddInstanceToGroupEvent, ITerminalEditorService, ITerminalExternalLinkProvider, ITerminalFindHost, ITerminalGroup, ITerminalGroupService, ITerminalInstance, ITerminalInstanceHost, ITerminalInstanceService, ITerminalLocationOptions, ITerminalService, ITerminalServiceNativeDelegate, TerminalConnectionState, TerminalEditorLocation } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; -import { TerminalEditor } from 'vs/workbench/contrib/terminal/browser/terminalEditor'; import { getColorStyleContent, getUriClasses } from 'vs/workbench/contrib/terminal/browser/terminalIcon'; import { getInstanceFromResource, getTerminalUri, parseTerminalUri } from 'vs/workbench/contrib/terminal/browser/terminalUri'; import { TerminalViewPane } from 'vs/workbench/contrib/terminal/browser/terminalView'; import { IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalConfigHelper, ITerminalBackend, ITerminalProcessExtHostProxy, ITerminalProfileService, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { formatMessageForTerminal, terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings'; -import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; +import { formatMessageForTerminal } from 'vs/workbench/contrib/terminal/common/terminalStrings'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ILifecycleService, ShutdownReason, WillShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; @@ -51,10 +48,10 @@ export class TerminalService implements ITerminalService { private _hostActiveTerminals: Map = new Map(); - private _isShuttingDown: boolean; + private _isShuttingDown: boolean = false; private _backgroundedTerminalInstances: ITerminalInstance[] = []; private _backgroundedTerminalDisposables: Map = new Map(); - private _findState: FindReplaceState; + private _findState: FindReplaceState = new FindReplaceState(); private _linkProviders: Set = new Set(); private _linkProviderDisposables: Map = new Map(); private _processSupportContextKey: IContextKey; @@ -138,7 +135,6 @@ export class TerminalService implements ITerminalService { constructor( @IContextKeyService private _contextKeyService: IContextKeyService, - @ILabelService labelService: ILabelService, @ILifecycleService lifecycleService: ILifecycleService, @IDialogService private _dialogService: IDialogService, @IInstantiationService private _instantiationService: IInstantiationService, @@ -149,47 +145,12 @@ export class TerminalService implements ITerminalService { @ITerminalEditorService private readonly _terminalEditorService: ITerminalEditorService, @ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService, @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService, - @IEditorResolverService editorResolverService: IEditorResolverService, @IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService, @ITerminalProfileService private readonly _terminalProfileService: ITerminalProfileService, @IExtensionService private readonly _extensionService: IExtensionService, @INotificationService private readonly _notificationService: INotificationService ) { - this._isShuttingDown = false; - this._findState = new FindReplaceState(); - this._configHelper = _instantiationService.createInstance(TerminalConfigHelper); - editorResolverService.registerEditor( - `${Schemas.vscodeTerminal}:/**`, - { - id: TerminalEditor.ID, - label: terminalStrings.terminal, - priority: RegisteredEditorPriority.exclusive - }, - { - canHandleDiff: false, - canSupportResource: uri => uri.scheme === Schemas.vscodeTerminal, - singlePerResource: true - }, - ({ resource, options }) => { - let instance = this.getInstanceFromResource(resource); - if (instance) { - const sourceGroup = this._terminalGroupService.getGroupForInstance(instance); - if (sourceGroup) { - sourceGroup.removeInstance(instance); - } - } - const resolvedResource = this._terminalEditorService.resolveResource(instance || resource); - const editor = this._terminalEditorService.getInputFromResource(resolvedResource) || { editor: resolvedResource }; - return { - editor, - options: { - ...options, - pinned: true, - forceReload: true, - override: TerminalEditor.ID - } - }; - }); + this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper); // the below avoids having to poll routinely. // we update detected profiles when an instance is created so that, // for example, we detect if you've installed a pwsh @@ -198,7 +159,7 @@ export class TerminalService implements ITerminalService { this._forwardInstanceHostEvents(this._terminalGroupService); this._forwardInstanceHostEvents(this._terminalEditorService); this._terminalGroupService.onDidChangeActiveGroup(this._onDidChangeActiveGroup.fire, this._onDidChangeActiveGroup); - _terminalInstanceService.onDidCreateInstance(instance => { + this._terminalInstanceService.onDidCreateInstance(instance => { this._initInstanceListeners(instance); this._onDidCreateInstance.fire(instance); }); @@ -223,18 +184,10 @@ export class TerminalService implements ITerminalService { lifecycleService.onBeforeShutdown(async e => e.veto(this._onBeforeShutdown(e.reason), 'veto.terminal')); lifecycleService.onWillShutdown(e => this._onWillShutdown(e)); - // Register a resource formatter for terminal URIs - labelService.registerFormatter({ - scheme: Schemas.vscodeTerminal, - formatting: { - label: '${path}', - separator: '' - } - }); - // Create async as the class depends on `this` timeout(0).then(() => this._instantiationService.createInstance(TerminalEditorStyle, document.head)); } + async showProfileQuickPick(type: 'setDefault' | 'createInstance', cwd?: string | URI): Promise { const quickPick = this._instantiationService.createInstance(TerminalProfileQuickpick); const result = await quickPick.showAndGetResult(type); @@ -382,8 +335,8 @@ export class TerminalService implements ITerminalService { instance.hasChildProcesses && (this.configHelper.config.confirmOnKill === 'panel' || this.configHelper.config.confirmOnKill === 'always')) { - const notConfirmed = await this._showTerminalCloseConfirmation(true); - if (notConfirmed) { + const veto = await this._showTerminalCloseConfirmation(true); + if (veto) { return; } } @@ -734,6 +687,7 @@ export class TerminalService implements ITerminalService { } if (source.target !== TerminalLocation.Editor) { + await this._terminalGroupService.showPanel(true); return; } source.target = TerminalLocation.Panel; @@ -910,35 +864,6 @@ export class TerminalService implements ITerminalService { return instance?.target === TerminalLocation.Editor ? this._terminalEditorService : this._terminalGroupService; } - private _convertProfileToShellLaunchConfig(shellLaunchConfigOrProfile?: IShellLaunchConfig | ITerminalProfile, cwd?: string | URI): IShellLaunchConfig { - if (shellLaunchConfigOrProfile && 'profileName' in shellLaunchConfigOrProfile) { - const profile = shellLaunchConfigOrProfile; - if (!profile.path) { - return shellLaunchConfigOrProfile; - } - return { - executable: profile.path, - args: profile.args, - env: profile.env, - icon: profile.icon, - color: profile.color, - name: profile.overrideName ? profile.profileName : undefined, - cwd - }; - } - - // A shell launch config was provided - if (shellLaunchConfigOrProfile) { - if (cwd) { - shellLaunchConfigOrProfile.cwd = cwd; - } - return shellLaunchConfigOrProfile; - } - - // Return empty shell launch config - return {}; - } - async createTerminal(options?: ICreateTerminalOptions): Promise { // Await the initialization of available profiles as long as this is not a pty terminal or a // local terminal in a remote workspace as profile won't be used in those cases and these @@ -952,7 +877,7 @@ export class TerminalService implements ITerminalService { } const config = options?.config || this._terminalProfileService.availableProfiles?.find(p => p.profileName === this._terminalProfileService.getDefaultProfileName()); - const shellLaunchConfig = config && 'extensionIdentifier' in config ? {} : this._convertProfileToShellLaunchConfig(config || {}); + const shellLaunchConfig = config && 'extensionIdentifier' in config ? {} : this._terminalInstanceService.convertProfileToShellLaunchConfig(config || {}); // Get the contributed profile if it was provided let contributedProfile = config && 'extensionIdentifier' in config ? config : undefined; @@ -965,7 +890,7 @@ export class TerminalService implements ITerminalService { // Launch the contributed profile if (contributedProfile) { const resolvedLocation = this.resolveLocation(options?.location); - const splitActiveTerminal = typeof options?.location === 'object' && 'splitActiveTerminal' in options.location ? options.location.splitActiveTerminal : false; + const splitActiveTerminal = typeof options?.location === 'object' && 'splitActiveTerminal' in options.location ? options.location.splitActiveTerminal : typeof options?.location === 'object' ? 'parentTerminal' in options.location : false; let location: TerminalLocation | { viewColumn: number, preserveState?: boolean } | { splitActiveTerminal: boolean } | undefined; if (splitActiveTerminal) { location = resolvedLocation === TerminalLocation.Editor ? { viewColumn: SIDE_GROUP } : { splitActiveTerminal: true }; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts index adae67849dc..c7ed0495e46 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts @@ -318,7 +318,6 @@ export class TerminalTabbedView extends Disposable { if (this._shouldShowTabs()) { this._splitView.resizeView(this._tabTreeIndex, this._getLastListWidth()); } - this._rerenderTabs(); } private _updateTheme(theme?: IColorTheme): void { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index 28c37528472..812b001a543 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -9,7 +9,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalGroupService, ITerminalInstance, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { localize } from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -548,7 +548,6 @@ class TerminalTabsDragAndDrop implements IListDragAndDrop { constructor( @ITerminalService private readonly _terminalService: ITerminalService, @ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService, - @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService, ) { this._primaryBackend = this._terminalService.getPrimaryBackend(); } @@ -710,8 +709,7 @@ class TerminalTabsDragAndDrop implements IListDragAndDrop { this._terminalService.setActiveInstance(instance); - const preparedPath = await this._terminalInstanceService.preparePathForTerminalAsync(path, instance.shellLaunchConfig.executable, instance.title, instance.shellType, instance.remoteAuthority); - instance.sendText(preparedPath, false); instance.focus(); + await instance.sendPath(path, false); } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index 048a1fe59c8..596a05c71e9 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -145,11 +145,15 @@ export class TerminalViewPane extends ViewPane { } else { this._onDidChangeViewWelcomeState.fire(); } + if (!this._terminalService.activeInstance?.shellLaunchConfig.extHostTerminalId) { + // showPanel is already called with !preserveFocus + // when extension host terminals are created + this._terminalGroupService.showPanel(true); + } if (hadTerminals) { this._terminalGroupService.activeGroup?.setVisible(visible); } - this._terminalGroupService.showPanel(true); } else { this._terminalGroupService.activeGroup?.setVisible(false); } diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 35bdcce9826..f916d9111c0 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -248,6 +248,7 @@ export interface ITerminalConfiguration { experimentalLinkProvider: boolean; localEchoLatencyThreshold: number; localEchoExcludePrograms: ReadonlyArray; + localEchoEnabled: 'auto' | 'on' | 'off'; localEchoStyle: 'bold' | 'dim' | 'italic' | 'underlined' | 'inverted' | string; enablePersistentSessions: boolean; tabs: { @@ -341,6 +342,7 @@ export interface ITerminalProcessManager extends IDisposable { readonly isDisconnected: boolean; readonly hasWrittenData: boolean; readonly hasChildProcesses: boolean; + readonly backend: ITerminalBackend | undefined; readonly onPtyDisconnect: Event; readonly onPtyReconnect: Event; @@ -430,6 +432,7 @@ export const enum TerminalCommandId { Kill = 'workbench.action.terminal.kill', KillEditor = 'workbench.action.terminal.killEditor', KillInstance = 'workbench.action.terminal.killInstance', + KillAll = 'workbench.action.terminal.killAll', QuickKill = 'workbench.action.terminal.quickKill', ConfigureTerminalSettings = 'workbench.action.terminal.openSettings', CopySelection = 'workbench.action.terminal.copySelection', @@ -450,6 +453,7 @@ export const enum TerminalCommandId { Unsplit = 'workbench.action.terminal.unsplit', UnsplitInstance = 'workbench.action.terminal.unsplitInstance', JoinInstance = 'workbench.action.terminal.joinInstance', + Join = 'workbench.action.terminal.join', Relaunch = 'workbench.action.terminal.relaunch', FocusPreviousPane = 'workbench.action.terminal.focusPreviousPane', ShowTabs = 'workbench.action.terminal.showTabs', diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index 84d3e578a02..f01fbc9cf8a 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -447,6 +447,17 @@ const terminalConfiguration: IConfigurationNode = { minimum: -1, default: 30, }, + [TerminalSettingId.LocalEchoEnabled]: { + description: localize('terminal.integrated.localEchoEnabled', "When local echo should be enabled. This will override `terminal.integrated.localEchoLatencyThreshold`"), + type: 'string', + enum: ['on', 'off', 'auto'], + enumDescriptions: [ + localize('terminal.integrated.localEchoEnabled.on', "Always enabled"), + localize('terminal.integrated.localEchoEnabled.off', "Always disabled"), + localize('terminal.integrated.localEchoEnabled.auto', "Enabled only for remote workspaces") + ], + default: 'auto' + }, [TerminalSettingId.LocalEchoExcludePrograms]: { description: localize('terminal.integrated.localEchoExcludePrograms', "Experimental: local echo will be disabled when any of these program names are found in the terminal title."), type: 'array', diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts index 36b9db9382f..1db307b4eb5 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { strictEqual } from 'assert'; +import { deepStrictEqual, strictEqual } from 'assert'; import { isWindows } from 'vs/base/common/platform'; -import { TerminalLabelComputer } from 'vs/workbench/contrib/terminal/browser/terminalInstance'; +import { TerminalLabelComputer, parseExitResult } from 'vs/workbench/contrib/terminal/browser/terminalInstance'; import { IWorkspaceContextService, toWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; @@ -15,6 +15,7 @@ import { fixPath, getUri } from 'vs/workbench/contrib/search/test/browser/queryB import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ProcessState } from 'vs/workbench/contrib/terminal/common/terminal'; import { basename } from 'vs/base/common/path'; function createInstance(partial?: Partial): Pick { @@ -40,7 +41,113 @@ const ROOT_2 = fixPath(root2); const emptyRoot = '/foo'; const ROOT_EMPTY = fixPath(emptyRoot); suite('Workbench - TerminalInstance', () => { - suite('refreshLabel', () => { + suite('parseExitResult', () => { + test('should return no message for exit code = undefined', () => { + deepStrictEqual( + parseExitResult(undefined, {}, ProcessState.KilledDuringLaunch, undefined), + { code: undefined, message: undefined } + ); + deepStrictEqual( + parseExitResult(undefined, {}, ProcessState.KilledByUser, undefined), + { code: undefined, message: undefined } + ); + deepStrictEqual( + parseExitResult(undefined, {}, ProcessState.KilledByProcess, undefined), + { code: undefined, message: undefined } + ); + }); + test('should return no message for exit code = 0', () => { + deepStrictEqual( + parseExitResult(0, {}, ProcessState.KilledDuringLaunch, undefined), + { code: 0, message: undefined } + ); + deepStrictEqual( + parseExitResult(0, {}, ProcessState.KilledByUser, undefined), + { code: 0, message: undefined } + ); + deepStrictEqual( + parseExitResult(0, {}, ProcessState.KilledDuringLaunch, undefined), + { code: 0, message: undefined } + ); + }); + test('should return friendly message when executable is specified for non-zero exit codes', () => { + deepStrictEqual( + parseExitResult(1, { executable: 'foo' }, ProcessState.KilledDuringLaunch, undefined), + { code: 1, message: 'The terminal process "foo" failed to launch (exit code: 1).' } + ); + deepStrictEqual( + parseExitResult(1, { executable: 'foo' }, ProcessState.KilledByUser, undefined), + { code: 1, message: 'The terminal process "foo" terminated with exit code: 1.' } + ); + deepStrictEqual( + parseExitResult(1, { executable: 'foo' }, ProcessState.KilledByProcess, undefined), + { code: 1, message: 'The terminal process "foo" terminated with exit code: 1.' } + ); + }); + test('should return friendly message when executable and args are specified for non-zero exit codes', () => { + deepStrictEqual( + parseExitResult(1, { executable: 'foo', args: ['bar', 'baz'] }, ProcessState.KilledDuringLaunch, undefined), + { code: 1, message: `The terminal process "foo 'bar', 'baz'" failed to launch (exit code: 1).` } + ); + deepStrictEqual( + parseExitResult(1, { executable: 'foo', args: ['bar', 'baz'] }, ProcessState.KilledByUser, undefined), + { code: 1, message: `The terminal process "foo 'bar', 'baz'" terminated with exit code: 1.` } + ); + deepStrictEqual( + parseExitResult(1, { executable: 'foo', args: ['bar', 'baz'] }, ProcessState.KilledByProcess, undefined), + { code: 1, message: `The terminal process "foo 'bar', 'baz'" terminated with exit code: 1.` } + ); + }); + test('should return friendly message when executable and arguments are omitted for non-zero exit codes', () => { + deepStrictEqual( + parseExitResult(1, {}, ProcessState.KilledDuringLaunch, undefined), + { code: 1, message: `The terminal process failed to launch (exit code: 1).` } + ); + deepStrictEqual( + parseExitResult(1, {}, ProcessState.KilledByUser, undefined), + { code: 1, message: `The terminal process terminated with exit code: 1.` } + ); + deepStrictEqual( + parseExitResult(1, {}, ProcessState.KilledByProcess, undefined), + { code: 1, message: `The terminal process terminated with exit code: 1.` } + ); + }); + test('should ignore pty host-related errors', () => { + deepStrictEqual( + parseExitResult({ message: 'Could not find pty with id 16' }, {}, ProcessState.KilledDuringLaunch, undefined), + { code: undefined, message: undefined } + ); + }); + test('should format conpty failure code 5', () => { + deepStrictEqual( + parseExitResult({ code: 5, message: 'A native exception occurred during launch (Cannot create process, error code: 5)' }, { executable: 'foo' }, ProcessState.KilledDuringLaunch, undefined), + { code: 5, message: `The terminal process failed to launch: Access was denied to the path containing your executable "foo". Manage and change your permissions to get this to work.` } + ); + }); + test('should format conpty failure code 267', () => { + deepStrictEqual( + parseExitResult({ code: 267, message: 'A native exception occurred during launch (Cannot create process, error code: 267)' }, {}, ProcessState.KilledDuringLaunch, '/foo'), + { code: 267, message: `The terminal process failed to launch: Invalid starting directory "/foo", review your terminal.integrated.cwd setting.` } + ); + }); + test('should format conpty failure code 1260', () => { + deepStrictEqual( + parseExitResult({ code: 1260, message: 'A native exception occurred during launch (Cannot create process, error code: 1260)' }, { executable: 'foo' }, ProcessState.KilledDuringLaunch, undefined), + { code: 1260, message: `The terminal process failed to launch: Windows cannot open this program because it has been prevented by a software restriction policy. For more information, open Event Viewer or contact your system Administrator.` } + ); + }); + test('should format generic failures', () => { + deepStrictEqual( + parseExitResult({ code: 123, message: 'A native exception occurred during launch (Cannot create process, error code: 123)' }, {}, ProcessState.KilledDuringLaunch, undefined), + { code: 123, message: `The terminal process failed to launch: A native exception occurred during launch (Cannot create process, error code: 123).` } + ); + deepStrictEqual( + parseExitResult({ code: 123, message: 'foo' }, {}, ProcessState.KilledDuringLaunch, undefined), + { code: 123, message: `The terminal process failed to launch: foo.` } + ); + }); + }); + suite('TerminalLabelComputer', () => { let configurationService: TestConfigurationService; let terminalLabelComputer: TerminalLabelComputer; let instantiationService: TestInstantiationService; diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalInstanceService.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalInstanceService.test.ts new file mode 100644 index 00000000000..038abf5d8a2 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalInstanceService.test.ts @@ -0,0 +1,122 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { deepStrictEqual } from 'assert'; +import { URI } from 'vs/base/common/uri'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; +import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { TerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminalInstanceService'; +import { ITerminalProfile } from 'vs/platform/terminal/common/terminal'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; + +suite('Workbench - TerminalInstanceService', () => { + let instantiationService: TestInstantiationService; + let terminalInstanceService: ITerminalInstanceService; + + setup(async () => { + instantiationService = new TestInstantiationService(); + // TODO: Should be able to create these services without this config set + instantiationService.stub(IConfigurationService, new TestConfigurationService({ + terminal: { + integrated: { + fontWeight: 'normal' + } + } + })); + instantiationService.stub(IContextKeyService, instantiationService.createInstance(ContextKeyService)); + + terminalInstanceService = instantiationService.createInstance(TerminalInstanceService); + }); + + suite('convertProfileToShellLaunchConfig', () => { + test('should return an empty shell launch config when undefined is provided', () => { + deepStrictEqual(terminalInstanceService.convertProfileToShellLaunchConfig(), {}); + deepStrictEqual(terminalInstanceService.convertProfileToShellLaunchConfig(undefined), {}); + }); + test('should return the same shell launch config when provided', () => { + deepStrictEqual( + terminalInstanceService.convertProfileToShellLaunchConfig({}), + {} + ); + deepStrictEqual( + terminalInstanceService.convertProfileToShellLaunchConfig({ executable: '/foo' }), + { executable: '/foo' } + ); + deepStrictEqual( + terminalInstanceService.convertProfileToShellLaunchConfig({ executable: '/foo', cwd: '/bar', args: ['a', 'b'] }), + { executable: '/foo', cwd: '/bar', args: ['a', 'b'] } + ); + deepStrictEqual( + terminalInstanceService.convertProfileToShellLaunchConfig({ executable: '/foo' }, '/bar'), + { executable: '/foo', cwd: '/bar' } + ); + deepStrictEqual( + terminalInstanceService.convertProfileToShellLaunchConfig({ executable: '/foo', cwd: '/bar' }, '/baz'), + { executable: '/foo', cwd: '/baz' } + ); + }); + test('should convert a provided profile to a shell launch config', () => { + deepStrictEqual( + terminalInstanceService.convertProfileToShellLaunchConfig({ + profileName: 'abc', + path: '/foo', + isDefault: true + }), + { + args: undefined, + color: undefined, + cwd: undefined, + env: undefined, + executable: '/foo', + icon: undefined, + name: undefined + } + ); + const icon = URI.file('/icon'); + deepStrictEqual( + terminalInstanceService.convertProfileToShellLaunchConfig({ + profileName: 'abc', + path: '/foo', + isDefault: true, + args: ['a', 'b'], + color: 'color', + env: { test: 'TEST' }, + icon + } as ITerminalProfile, '/bar'), + { + args: ['a', 'b'], + color: 'color', + cwd: '/bar', + env: { test: 'TEST' }, + executable: '/foo', + icon, + name: undefined + } + ); + }); + test('should respect overrideName in profile', () => { + deepStrictEqual( + terminalInstanceService.convertProfileToShellLaunchConfig({ + profileName: 'abc', + path: '/foo', + isDefault: true, + overrideName: true + }), + { + args: undefined, + color: undefined, + cwd: undefined, + env: undefined, + executable: '/foo', + icon: undefined, + name: 'abc' + } + ); + }); + }); +}); diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts new file mode 100644 index 00000000000..ab9dbe73b5d --- /dev/null +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts @@ -0,0 +1,183 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { fail } from 'assert'; +import { TerminalLocation } from 'vs/platform/terminal/common/terminal'; +import { TerminalService } from 'vs/workbench/contrib/terminal/browser/terminalService'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TestLifecycleService, TestTerminalEditorService, TestTerminalGroupService, TestTerminalInstanceService, TestTerminalProfileService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; +import { ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { TestRemoteAgentService } from 'vs/workbench/services/remote/test/common/testServices'; + +suite('Workbench - TerminalService', () => { + let instantiationService: TestInstantiationService; + let terminalService: TerminalService; + let configurationService: TestConfigurationService; + let dialogService: TestDialogService; + + setup(async () => { + dialogService = new TestDialogService(); + configurationService = new TestConfigurationService({ + terminal: { + integrated: { + fontWeight: 'normal' + } + } + }); + + instantiationService = new TestInstantiationService(); + instantiationService.stub(IConfigurationService, configurationService); + instantiationService.stub(IContextKeyService, instantiationService.createInstance(ContextKeyService)); + instantiationService.stub(ILifecycleService, new TestLifecycleService()); + instantiationService.stub(IThemeService, new TestThemeService()); + instantiationService.stub(ITerminalEditorService, new TestTerminalEditorService()); + instantiationService.stub(ITerminalGroupService, new TestTerminalGroupService()); + instantiationService.stub(ITerminalInstanceService, new TestTerminalInstanceService()); + instantiationService.stub(ITerminalProfileService, new TestTerminalProfileService()); + instantiationService.stub(IRemoteAgentService, new TestRemoteAgentService()); + instantiationService.stub(IRemoteAgentService, 'getConnection', null); + instantiationService.stub(IDialogService, dialogService); + + terminalService = instantiationService.createInstance(TerminalService); + instantiationService.stub(ITerminalService, terminalService); + }); + + suite('safeDisposeTerminal', () => { + test('should not show prompt when confirmOnKill is never', async () => { + setConfirmOnKill(configurationService, 'never'); + await new Promise(r => { + terminalService.safeDisposeTerminal({ + target: TerminalLocation.Editor, + hasChildProcesses: true, + dispose: () => r() + } as Partial as any); + }); + await new Promise(r => { + terminalService.safeDisposeTerminal({ + target: TerminalLocation.Panel, + hasChildProcesses: true, + dispose: () => r() + } as Partial as any); + }); + }); + test('should not show prompt when any terminal editor is closed (handled by editor itself)', async () => { + setConfirmOnKill(configurationService, 'editor'); + await new Promise(r => { + terminalService.safeDisposeTerminal({ + target: TerminalLocation.Editor, + hasChildProcesses: true, + dispose: () => r() + } as Partial as any); + }); + setConfirmOnKill(configurationService, 'always'); + await new Promise(r => { + terminalService.safeDisposeTerminal({ + target: TerminalLocation.Editor, + hasChildProcesses: true, + dispose: () => r() + } as Partial as any); + }); + }); + test('should not show prompt when confirmOnKill is editor and panel terminal is closed', async () => { + setConfirmOnKill(configurationService, 'editor'); + await new Promise(r => { + terminalService.safeDisposeTerminal({ + target: TerminalLocation.Panel, + hasChildProcesses: true, + dispose: () => r() + } as Partial as any); + }); + }); + test('should show prompt when confirmOnKill is panel and panel terminal is closed', async () => { + setConfirmOnKill(configurationService, 'panel'); + // No child process cases + dialogService.setConfirmResult({ confirmed: false }); + await new Promise(r => { + terminalService.safeDisposeTerminal({ + target: TerminalLocation.Panel, + hasChildProcesses: false, + dispose: () => r() + } as Partial as any); + }); + dialogService.setConfirmResult({ confirmed: true }); + await new Promise(r => { + terminalService.safeDisposeTerminal({ + target: TerminalLocation.Panel, + hasChildProcesses: false, + dispose: () => r() + } as Partial as any); + }); + // Child process cases + dialogService.setConfirmResult({ confirmed: false }); + await terminalService.safeDisposeTerminal({ + target: TerminalLocation.Panel, + hasChildProcesses: true, + dispose: () => fail() + } as Partial as any); + dialogService.setConfirmResult({ confirmed: true }); + await new Promise(r => { + terminalService.safeDisposeTerminal({ + target: TerminalLocation.Panel, + hasChildProcesses: true, + dispose: () => r() + } as Partial as any); + }); + }); + test('should show prompt when confirmOnKill is always and panel terminal is closed', async () => { + setConfirmOnKill(configurationService, 'always'); + // No child process cases + dialogService.setConfirmResult({ confirmed: false }); + await new Promise(r => { + terminalService.safeDisposeTerminal({ + target: TerminalLocation.Panel, + hasChildProcesses: false, + dispose: () => r() + } as Partial as any); + }); + dialogService.setConfirmResult({ confirmed: true }); + await new Promise(r => { + terminalService.safeDisposeTerminal({ + target: TerminalLocation.Panel, + hasChildProcesses: false, + dispose: () => r() + } as Partial as any); + }); + // Child process cases + dialogService.setConfirmResult({ confirmed: false }); + await terminalService.safeDisposeTerminal({ + target: TerminalLocation.Panel, + hasChildProcesses: true, + dispose: () => fail() + } as Partial as any); + dialogService.setConfirmResult({ confirmed: true }); + await new Promise(r => { + terminalService.safeDisposeTerminal({ + target: TerminalLocation.Panel, + hasChildProcesses: true, + dispose: () => r() + } as Partial as any); + }); + }); + }); +}); + +async function setConfirmOnKill(configurationService: TestConfigurationService, value: 'never' | 'always' | 'panel' | 'editor') { + await configurationService.setUserConfiguration('terminal', { integrated: { confirmOnKill: value } }); + configurationService.onDidChangeConfigurationEmitter.fire({ + affectsConfiguration: () => true, + affectedKeys: ['terminal.integrated.confirmOnKill'] + } as any); +} diff --git a/src/vs/workbench/contrib/testing/browser/explorerProjections/display.ts b/src/vs/workbench/contrib/testing/browser/explorerProjections/display.ts index c26642329d4..548ee8b1da8 100644 --- a/src/vs/workbench/contrib/testing/browser/explorerProjections/display.ts +++ b/src/vs/workbench/contrib/testing/browser/explorerProjections/display.ts @@ -3,5 +3,4 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// allow-any-unicode-next-line -export const flatTestItemDelimiter = ' › '; +export const flatTestItemDelimiter = ' \u203A '; diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index fa95f119d62..a3cdd412cc1 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -22,14 +22,15 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IProductService } from 'vs/platform/product/common/productService'; import { asText, IRequestService } from 'vs/platform/request/common/request'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { DEFAULT_MARKDOWN_STYLES, renderMarkdownDocument } from 'vs/workbench/contrib/markdown/browser/markdownDocumentRenderer'; import { WebviewInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditorInput'; import { IWebviewWorkbenchService } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ACTIVE_GROUP, IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; +import { getTelemetryLevel, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; export class ReleaseNotesManager { @@ -44,7 +45,7 @@ export class ReleaseNotesManager { @IModeService private readonly _modeService: IModeService, @IOpenerService private readonly _openerService: IOpenerService, @IRequestService private readonly _requestService: IRequestService, - @ITelemetryService private readonly _telemetryService: ITelemetryService, + @IConfigurationService private readonly _configurationService: IConfigurationService, @IEditorService private readonly _editorService: IEditorService, @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, @IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService, @@ -195,11 +196,9 @@ export class ReleaseNotesManager { } private async addGAParameters(uri: URI, origin: string, experiment = '1'): Promise { - if (supportsTelemetry(this._productService, this._environmentService)) { + if (supportsTelemetry(this._productService, this._environmentService) && getTelemetryLevel(this._configurationService) === TelemetryLevel.USAGE) { if (uri.scheme === 'https' && uri.authority === 'code.visualstudio.com') { - const info = await this._telemetryService.getTelemetryInfo(); - - return uri.with({ query: `${uri.query ? uri.query + '&' : ''}utm_source=VsCode&utm_medium=${encodeURIComponent(origin)}&utm_campaign=${encodeURIComponent(info.instanceId)}&utm_content=${encodeURIComponent(experiment)}` }); + return uri.with({ query: `${uri.query ? uri.query + '&' : ''}utm_source=VsCode&utm_medium=${encodeURIComponent(origin)}&utm_content=${encodeURIComponent(experiment)}` }); } } return uri; diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index 62f557f75ec..16cc137a10b 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -994,13 +994,18 @@ onDomReady(() => { if (!target) { return; } + + // Reset selection so we start search after current find result + const selection = target.contentWindow.getSelection(); + selection.collapse(selection.anchorNode); + const didFind = (/** @type {any} */ (target.contentWindow)).find( data.value, - false, + /* caseSensitive*/ false, /* backwards*/ data.previous, /* wrapAround*/ true, - false, - /*aSearchInFrames*/ true, + /* wholeWord */ false, + /* searchInFrames*/ false, false); hostMessaging.postMessage('did-find', didFind); }); diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index b645e966772..6797ecaec0b 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -14,6 +14,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; import { localize } from 'vs/nls'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; @@ -93,6 +94,9 @@ namespace WebviewState { export class WebviewElement extends Disposable implements IWebview, WebviewFindDelegate { + public readonly id: string; + private readonly iframeId: string; + protected get platform(): string { return 'browser'; } private readonly _expectedServiceWorkerVersion = 2; // Keep this in sync with the version in service-worker.js @@ -136,7 +140,7 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD public readonly checkImeCompletionState = true; constructor( - public readonly id: string, + id: string, private readonly options: WebviewOptions, contentOptions: WebviewContentOptions, public extension: WebviewExtensionDescription | undefined, @@ -155,6 +159,9 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD ) { super(); + this.id = id; + this.iframeId = generateUuid(); + this.content = { html: '', options: contentOptions, @@ -312,7 +319,7 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD })); this._register(addDisposableListener(window, 'message', e => { - if (e?.data?.target === this.id) { + if (e?.data?.target === this.iframeId) { if (e.origin !== this.webviewContentOrigin) { console.log(`Skipped renderer receiving message due to mismatched origins: ${e.origin} ${this.webviewContentOrigin}`); return; @@ -412,7 +419,7 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD private initElement(extension: WebviewExtensionDescription | undefined, options: WebviewOptions) { // The extensionId and purpose in the URL are used for filtering in js-debug: const params: { [key: string]: string } = { - id: this.id, + id: this.iframeId, swVersion: String(this._expectedServiceWorkerVersion), extensionId: extension?.id.value ?? '', platform: this.platform, diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.ts index 28657c3f0a6..b7d52c8e8cc 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.ts @@ -13,19 +13,19 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle' import { EditorActivation } from 'vs/platform/editor/common/editor'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { GroupIdentifier } from 'vs/workbench/common/editor'; -import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; -import { IWebviewService, WebviewContentOptions, WebviewExtensionDescription, WebviewOptions, IOverlayWebview } from 'vs/workbench/contrib/webview/browser/webview'; +import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { IOverlayWebview, IWebviewService, WebviewContentOptions, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewIconManager, WebviewIcons } from 'vs/workbench/contrib/webviewPanel/browser/webviewIconManager'; -import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; import { WebviewInput } from './webviewEditorInput'; export const IWebviewWorkbenchService = createDecorator('webviewEditorService'); export interface ICreateWebViewShowOptions { - group: IEditorGroup | GroupIdentifier | ACTIVE_GROUP_TYPE | SIDE_GROUP_TYPE; - preserveFocus: boolean; + readonly group?: IEditorGroup | GroupIdentifier | ACTIVE_GROUP_TYPE | SIDE_GROUP_TYPE; + readonly preserveFocus?: boolean; } export interface IWebviewWorkbenchService { @@ -57,7 +57,7 @@ export interface IWebviewWorkbenchService { revealWebview( webview: WebviewInput, - group: IEditorGroup, + group: IEditorGroup | GroupIdentifier | ACTIVE_GROUP_TYPE | SIDE_GROUP_TYPE, preserveFocus: boolean ): void; @@ -168,7 +168,6 @@ export class WebviewEditorService extends Disposable implements IWebviewWorkbenc private readonly _iconManager: WebviewIconManager; constructor( - @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, @IEditorService private readonly _editorService: IEditorService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IWebviewService private readonly _webviewService: IWebviewService, @@ -241,27 +240,20 @@ export class WebviewEditorService extends Disposable implements IWebviewWorkbenc public revealWebview( webview: WebviewInput, - group: IEditorGroup, + group: IEditorGroup | GroupIdentifier | ACTIVE_GROUP_TYPE | SIDE_GROUP_TYPE, preserveFocus: boolean ): void { const topLevelEditor = this.findTopLevelEditorForWebview(webview); - if (webview.group === group.id) { - if (this._editorService.activeEditor === topLevelEditor) { - return; - } - - this._editorService.openEditor(topLevelEditor, { - preserveFocus, - // preserve pre 1.38 behaviour to not make group active when preserveFocus: true - // but make sure to restore the editor to fix https://github.com/microsoft/vscode/issues/79633 - activation: preserveFocus ? EditorActivation.RESTORE : undefined - }, webview.group); - } else { - const groupView = this._editorGroupService.getGroup(webview.group!); - if (groupView) { - groupView.moveEditor(topLevelEditor, group, { preserveFocus }); - } + if (this._editorService.activeEditor === topLevelEditor) { + return; } + + this._editorService.openEditor(topLevelEditor, { + preserveFocus, + // preserve pre 1.38 behaviour to not make group active when preserveFocus: true + // but make sure to restore the editor to fix https://github.com/microsoft/vscode/issues/79633 + activation: preserveFocus ? EditorActivation.RESTORE : undefined + }, group); } private findTopLevelEditorForWebview(webview: WebviewInput): EditorInput { diff --git a/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts b/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts index da5323be01f..91f6b79e2e5 100644 --- a/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts +++ b/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts @@ -73,8 +73,8 @@ function parseGroupAndOrder(welcome: ViewWelcome, contribution: IExtensionPointU let group: string | undefined; let order: number | undefined; if (welcome.group) { - if (!isProposedApiEnabled(contribution.description)) { - contribution.collector.warn(nls.localize('ViewsWelcomeExtensionPoint.proposedAPI', "The viewsWelcome contribution in '{0}' requires 'enableProposedApi' to be enabled.", contribution.description.identifier.value)); + if (!isProposedApiEnabled(contribution.description, 'contribViewsWelcome')) { + contribution.collector.warn(nls.localize('ViewsWelcomeExtensionPoint.proposedAPI', "The viewsWelcome contribution in '{0}' requires 'enabledApiProposals: [\"contribViewsWelcome\"]' in order to use the 'group' proposed property.", contribution.description.identifier.value)); return { group, order }; } diff --git a/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts b/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts index c6629c19a87..39a48446c2c 100644 --- a/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts +++ b/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts @@ -65,7 +65,7 @@ const viewsWelcomeExtensionPointSchema = Object.freeze { - if (this.input instanceof WalkThroughInput) { - this.saveTextEditorViewState(this.input); - } - const store = new DisposableStore(); this.contentDisposables.push(store); diff --git a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts index 0786d610505..405141da521 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts @@ -62,7 +62,7 @@ const checkListIcon = registerIcon('workspace-trust-editor-check', Codicon.check const xListIcon = registerIcon('workspace-trust-editor-cross', Codicon.x, localize('xListIcon', 'Icon for the cross in the workspace trust editor.')); const folderPickerIcon = registerIcon('workspace-trust-editor-folder-picker', Codicon.folder, localize('folderPickerIcon', 'Icon for the pick folder icon in the workspace trust editor.')); const editIcon = registerIcon('workspace-trust-editor-edit-folder', Codicon.edit, localize('editIcon', 'Icon for the edit folder icon in the workspace trust editor.')); -const removeIcon = registerIcon('workspace-trust-editor-remove-folder', Codicon.edit, localize('removeIcon', 'Icon for the remove folder icon in the workspace trust editor.')); +const removeIcon = registerIcon('workspace-trust-editor-remove-folder', Codicon.close, localize('removeIcon', 'Icon for the remove folder icon in the workspace trust editor.')); interface ITrustedUriItem { parentOfWorkspaceItem: boolean; diff --git a/src/vs/workbench/electron-sandbox/shared.desktop.main.ts b/src/vs/workbench/electron-sandbox/shared.desktop.main.ts index 9bfb3976893..f4a7ba21f27 100644 --- a/src/vs/workbench/electron-sandbox/shared.desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/shared.desktop.main.ts @@ -317,9 +317,11 @@ export abstract class SharedDesktopMain extends Disposable { if (!workspaceInitializationPayload) { let id: string; if (this.configuration.backupPath) { - id = basename(this.configuration.backupPath); // we know the backupPath must be a unique path so we leverage its name as workspace ID + // we know the backupPath must be a unique path so we leverage its name as workspace ID + id = basename(this.configuration.backupPath); } else if (environmentService.isExtensionDevelopment) { - id = 'ext-dev'; // extension development window never stores backups and is a singleton + // fallback to a reserved identifier when in extension development where backups are not stored + id = 'ext-dev'; } else { throw new Error('Unexpected window configuration without backupPath'); } diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index 69a722795bf..0888f24f9d3 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -163,13 +163,16 @@ export function readAllowedExtensions(storageService: IStorageService, providerI return trustedExtensions; } -export interface SessionRequest { +// OAuth2 spec prohibits space in a scope, so use that to join them. +const SCOPESLIST_SEPARATOR = ' '; + +interface SessionRequest { disposables: IDisposable[]; requestingExtensionIds: string[]; } -export interface SessionRequestInfo { - [scopes: string]: SessionRequest; +interface SessionRequestInfo { + [scopesList: string]: SessionRequest; } CommandsRegistry.registerCommand('workbench.getCodeExchangeProxyEndpoints', function (accessor, _) { @@ -347,7 +350,7 @@ export class AuthenticationService extends Disposable implements IAuthentication } Object.keys(existingRequestsForProvider).forEach(requestedScopes => { - if (addedSessions.some(session => session.scopes.slice().join('') === requestedScopes)) { + if (addedSessions.some(session => session.scopes.slice().join(SCOPESLIST_SEPARATOR) === requestedScopes)) { const sessionRequest = existingRequestsForProvider[requestedScopes]; sessionRequest?.disposables.forEach(item => item.dispose()); @@ -613,7 +616,7 @@ export class AuthenticationService extends Disposable implements IAuthentication if (provider) { const providerRequests = this._signInRequestItems.get(providerId); - const scopesList = scopes.join(''); + const scopesList = scopes.join(SCOPESLIST_SEPARATOR); const extensionHasExistingRequest = providerRequests && providerRequests[scopesList] && providerRequests[scopesList].requestingExtensionIds.includes(extensionId); @@ -622,10 +625,12 @@ export class AuthenticationService extends Disposable implements IAuthentication return; } + // Construct a commandId that won't clash with others generated here, nor likely with an extension's command + const commandId = `${providerId}:${extensionId}:signIn${Object.keys(providerRequests || []).length}`; const menuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, { group: '2_signInRequests', command: { - id: `${extensionId}signIn`, + id: commandId, title: nls.localize({ key: 'signInRequest', comment: [`The placeholder {0} will be replaced with an authentication provider's label. {1} will be replaced with an extension name. (1) is to indicate that this menu item contributes to a badge count.`] @@ -637,7 +642,7 @@ export class AuthenticationService extends Disposable implements IAuthentication }); const signInCommand = CommandsRegistry.registerCommand({ - id: `${extensionId}signIn`, + id: commandId, handler: async (accessor) => { const authenticationService = accessor.get(IAuthenticationService); const storageService = accessor.get(IStorageService); diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index f24e3a93f19..5c0ae2f19c6 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -12,11 +12,11 @@ import { Queue, Barrier, runWhenIdle, Promises } from 'vs/base/common/async'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { IWorkspaceContextService, Workspace as BaseWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, WorkspaceFolder, toWorkspaceFolder, isWorkspaceFolder, IWorkspaceFoldersWillChangeEvent } from 'vs/platform/workspace/common/workspace'; import { ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent, mergeChanges } from 'vs/platform/configuration/common/configurationModels'; -import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange, ConfigurationTargetToString } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange, ConfigurationTargetToString, IConfigurationUpdateOverrides, isConfigurationUpdateOverrides } from 'vs/platform/configuration/common/configuration'; import { Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, RestrictedSettings } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings, ConfigurationScope, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings, ConfigurationScope, IConfigurationPropertySchema, keyFromOverrideIdentifiers } from 'vs/platform/configuration/common/configurationRegistry'; import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IWorkspaceInitializationPayload, IEmptyWorkspaceIdentifier, useSlashForPath, getStoredWorkspaceFolder, isSingleFolderWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ConfigurationEditingService, EditableConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditingService'; @@ -305,18 +305,26 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } updateValue(key: string, value: any): Promise; - updateValue(key: string, value: any, overrides: IConfigurationOverrides): Promise; + updateValue(key: string, value: any, overrides: IConfigurationOverrides | IConfigurationUpdateOverrides): Promise; updateValue(key: string, value: any, target: ConfigurationTarget): Promise; - updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget): Promise; - updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget, donotNotifyError: boolean): Promise; + updateValue(key: string, value: any, overrides: IConfigurationOverrides | IConfigurationUpdateOverrides, target: ConfigurationTarget, donotNotifyError?: boolean): Promise; async updateValue(key: string, value: any, arg3?: any, arg4?: any, donotNotifyError?: any): Promise { await this.cyclicDependency; - const overrides = isConfigurationOverrides(arg3) ? arg3 : undefined; + const overrides: IConfigurationUpdateOverrides | undefined = isConfigurationUpdateOverrides(arg3) ? arg3 + : isConfigurationOverrides(arg3) ? { resource: arg3.resource, overrideIdentifiers: arg3.overrideIdentifier ? [arg3.overrideIdentifier] : undefined } : undefined; const target: ConfigurationTarget | undefined = overrides ? arg4 : arg3; const targets: ConfigurationTarget[] = target ? [target] : []; + if (overrides?.overrideIdentifiers) { + overrides.overrideIdentifiers = distinct(overrides.overrideIdentifiers); + overrides.overrideIdentifiers = overrides.overrideIdentifiers.length ? overrides.overrideIdentifiers : undefined; + } + if (!targets.length) { - const inspect = this.inspect(key, overrides); + if (overrides?.overrideIdentifiers && overrides.overrideIdentifiers.length > 1) { + throw new Error('Configuration Target is required while updating the value for multiple override identifiers'); + } + const inspect = this.inspect(key, { resource: overrides?.resource, overrideIdentifier: overrides?.overrideIdentifiers ? overrides.overrideIdentifiers[0] : undefined }); targets.push(...this.deriveConfigurationTargets(key, value, inspect)); // Remove the setting, if the value is same as default value and is updated only in user target @@ -856,7 +864,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat return validWorkspaceFolders; } - private async writeConfigurationValue(key: string, value: any, target: ConfigurationTarget, overrides: IConfigurationOverrides | undefined, donotNotifyError: boolean): Promise { + private async writeConfigurationValue(key: string, value: any, target: ConfigurationTarget, overrides: IConfigurationUpdateOverrides | undefined, donotNotifyError: boolean): Promise { if (target === ConfigurationTarget.DEFAULT) { throw new Error('Invalid configuration target'); } @@ -864,7 +872,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat if (target === ConfigurationTarget.MEMORY) { const previous = { data: this._configuration.toData(), workspace: this.workspace }; this._configuration.updateValue(key, value, overrides); - this.triggerConfigurationChange({ keys: overrides?.overrideIdentifier ? [keyFromOverrideIdentifier(overrides.overrideIdentifier), key] : [key], overrides: overrides?.overrideIdentifier ? [[overrides?.overrideIdentifier, [key]]] : [] }, previous, target); + this.triggerConfigurationChange({ keys: overrides?.overrideIdentifiers?.length ? [keyFromOverrideIdentifiers(overrides.overrideIdentifiers), key] : [key], overrides: overrides?.overrideIdentifiers?.length ? overrides.overrideIdentifiers.map(overrideIdentifier => ([overrideIdentifier, [key]])) : [] }, previous, target); return; } diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index bfaf5ecd7d6..c18ee164b64 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -13,11 +13,11 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IConfigurationService, IConfigurationOverrides, keyFromOverrideIdentifier } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, IConfigurationUpdateOverrides } from 'vs/platform/configuration/common/configuration'; import { FOLDER_SETTINGS_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY, USER_STANDALONE_CONFIGURATIONS, TASKS_DEFAULT, FOLDER_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; -import { OVERRIDE_PROPERTY_PATTERN, IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, keyFromOverrideIdentifiers, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IOpenSettingsOptions, IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; @@ -113,7 +113,7 @@ export interface IConfigurationEditingOptions { /** * Scope of configuration to be written into. */ - scopes?: IConfigurationOverrides; + scopes?: IConfigurationUpdateOverrides; } export const enum EditableConfigurationTarget { @@ -243,7 +243,7 @@ export class ConfigurationEditingService { return { insertSpaces, tabSize, eol }; } - private async onError(error: ConfigurationEditingError, operation: IConfigurationEditOperation, scopes: IConfigurationOverrides | undefined): Promise { + private async onError(error: ConfigurationEditingError, operation: IConfigurationEditOperation, scopes: IConfigurationUpdateOverrides | undefined): Promise { switch (error.code) { case ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION: this.onInvalidConfigurationError(error, operation); @@ -279,7 +279,7 @@ export class ConfigurationEditingService { } } - private onConfigurationFileDirtyError(error: ConfigurationEditingError, operation: IConfigurationEditOperation, scopes: IConfigurationOverrides | undefined): void { + private onConfigurationFileDirtyError(error: ConfigurationEditingError, operation: IConfigurationEditOperation, scopes: IConfigurationUpdateOverrides | undefined): void { const openStandAloneConfigurationActionLabel = operation.workspaceStandAloneConfigurationKey === TASKS_CONFIGURATION_KEY ? nls.localize('openTasksConfiguration', "Open Tasks Configuration") : operation.workspaceStandAloneConfigurationKey === LAUNCH_CONFIGURATION_KEY ? nls.localize('openLaunchConfiguration', "Open Launch Configuration") : null; @@ -475,7 +475,7 @@ export class ConfigurationEditingService { return parseErrors.length > 0; } - private async validate(target: EditableConfigurationTarget, operation: IConfigurationEditOperation, checkDirty: boolean, overrides: IConfigurationOverrides): Promise { + private async validate(target: EditableConfigurationTarget, operation: IConfigurationEditOperation, checkDirty: boolean, overrides: IConfigurationUpdateOverrides): Promise { const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); const configurationScope = configurationProperties[operation.key]?.scope; @@ -488,7 +488,7 @@ export class ConfigurationEditingService { */ if (!operation.workspaceStandAloneConfigurationKey) { const validKeys = this.configurationService.keys().default; - if (validKeys.indexOf(operation.key) < 0 && !OVERRIDE_PROPERTY_PATTERN.test(operation.key) && operation.value !== undefined) { + if (validKeys.indexOf(operation.key) < 0 && !OVERRIDE_PROPERTY_REGEX.test(operation.key) && operation.value !== undefined) { throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_UNKNOWN_KEY, target, operation); } } @@ -506,7 +506,7 @@ export class ConfigurationEditingService { } if (target === EditableConfigurationTarget.WORKSPACE) { - if (!operation.workspaceStandAloneConfigurationKey && !OVERRIDE_PROPERTY_PATTERN.test(operation.key)) { + if (!operation.workspaceStandAloneConfigurationKey && !OVERRIDE_PROPERTY_REGEX.test(operation.key)) { if (configurationScope === ConfigurationScope.APPLICATION) { throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_APPLICATION, target, operation); } @@ -521,14 +521,14 @@ export class ConfigurationEditingService { throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_TARGET, target, operation); } - if (!operation.workspaceStandAloneConfigurationKey && !OVERRIDE_PROPERTY_PATTERN.test(operation.key)) { + if (!operation.workspaceStandAloneConfigurationKey && !OVERRIDE_PROPERTY_REGEX.test(operation.key)) { if (configurationScope !== undefined && !FOLDER_SCOPES.includes(configurationScope)) { throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_CONFIGURATION, target, operation); } } } - if (overrides.overrideIdentifier) { + if (overrides.overrideIdentifiers?.length) { if (configurationScope !== ConfigurationScope.LANGUAGE_OVERRIDABLE) { throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_RESOURCE_LANGUAGE_CONFIGURATION, target, operation); } @@ -544,7 +544,7 @@ export class ConfigurationEditingService { } - private getConfigurationEditOperation(target: EditableConfigurationTarget, config: IConfigurationValue, overrides: IConfigurationOverrides): IConfigurationEditOperation { + private getConfigurationEditOperation(target: EditableConfigurationTarget, config: IConfigurationValue, overrides: IConfigurationUpdateOverrides): IConfigurationEditOperation { // Check for standalone workspace configurations if (config.key) { @@ -569,7 +569,7 @@ export class ConfigurationEditingService { } let key = config.key; - let jsonPath = overrides.overrideIdentifier ? [keyFromOverrideIdentifier(overrides.overrideIdentifier), key] : [key]; + let jsonPath = overrides.overrideIdentifiers?.length ? [keyFromOverrideIdentifiers(overrides.overrideIdentifiers), key] : [key]; if (target === EditableConfigurationTarget.USER_LOCAL || target === EditableConfigurationTarget.USER_REMOTE) { return { key, jsonPath, value: config.value, resource: withNullAsUndefined(this.getConfigurationFileResource(target, '', null)), target }; } diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index 412065dd3b2..95a1d40ad77 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -10,8 +10,8 @@ import { IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces import { Workspace } from 'vs/platform/workspace/common/workspace'; import { ResourceMap } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; -import { OVERRIDE_PROPERTY_PATTERN, overrideIdentifierFromKey } from 'vs/platform/configuration/common/configurationRegistry'; import { isBoolean } from 'vs/base/common/types'; +import { distinct } from 'vs/base/common/arrays'; export class WorkspaceConfigurationModelParser extends ConfigurationModelParser { @@ -154,10 +154,11 @@ export class Configuration extends BaseConfiguration { }; const keys = compare(this.allKeys(), other.allKeys()); const overrides: [string, string[]][] = []; - for (const key of keys) { - if (OVERRIDE_PROPERTY_PATTERN.test(key)) { - const overrideIdentifier = overrideIdentifierFromKey(key); - overrides.push([overrideIdentifier, compare(this.getAllKeysForOverrideIdentifier(overrideIdentifier), other.getAllKeysForOverrideIdentifier(overrideIdentifier), overrideIdentifier)]); + const allOverrideIdentifiers = distinct([...this.allOverrideIdentifiers(), ...other.allOverrideIdentifiers()]); + for (const overrideIdentifier of allOverrideIdentifiers) { + const keys = compare(this.getAllKeysForOverrideIdentifier(overrideIdentifier), other.getAllKeysForOverrideIdentifier(overrideIdentifier), overrideIdentifier); + if (keys.length) { + overrides.push([overrideIdentifier, keys]); } } return { keys, overrides }; diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index 6ea7e0ccaec..17f9dfb4c51 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -8,7 +8,7 @@ import * as sinon from 'sinon'; import { URI } from 'vs/base/common/uri'; import { Registry } from 'vs/platform/registry/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, keyFromOverrideIdentifiers } from 'vs/platform/configuration/common/configurationRegistry'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ConfigurationEditingErrorCode } from 'vs/workbench/services/configuration/common/configurationEditingService'; @@ -1012,9 +1012,38 @@ suite('WorkspaceConfigurationService - Folder', () => { .then(() => assert.strictEqual(testObject.getValue('configurationService.folder.testSetting'), 'value')); }); - test('update resource language configuration', () => { - return testObject.updateValue('configurationService.folder.languageSetting', 'value', { resource: workspaceService.getWorkspace().folders[0].uri }, ConfigurationTarget.WORKSPACE_FOLDER) - .then(() => assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting'), 'value')); + test('update language configuration using configuration overrides', async () => { + await testObject.updateValue('configurationService.folder.languageSetting', 'abcLangValue', { overrideIdentifier: 'abclang' }); + assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting', { overrideIdentifier: 'abclang' }), 'abcLangValue'); + }); + + test('update language configuration using configuration update overrides', async () => { + await testObject.updateValue('configurationService.folder.languageSetting', 'abcLangValue', { overrideIdentifiers: ['abclang'] }); + assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting', { overrideIdentifier: 'abclang' }), 'abcLangValue'); + }); + + test('update language configuration for multiple languages', async () => { + await testObject.updateValue('configurationService.folder.languageSetting', 'multiLangValue', { overrideIdentifiers: ['deflang', 'xyzlang'] }, ConfigurationTarget.USER); + assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting', { overrideIdentifier: 'deflang' }), 'multiLangValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting', { overrideIdentifier: 'xyzlang' }), 'multiLangValue'); + assert.deepStrictEqual(testObject.getValue(keyFromOverrideIdentifiers(['deflang', 'xyzlang'])), { 'configurationService.folder.languageSetting': 'multiLangValue' }); + }); + + test('update resource language configuration', async () => { + await testObject.updateValue('configurationService.folder.languageSetting', 'value', { resource: workspaceService.getWorkspace().folders[0].uri }, ConfigurationTarget.WORKSPACE_FOLDER); + assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting'), 'value'); + }); + + test('update resource language configuration for a language using configuration overrides', async () => { + assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting', { resource: workspaceService.getWorkspace().folders[0].uri, overrideIdentifier: 'jsonc' }), 'languageValue'); + await testObject.updateValue('configurationService.folder.languageSetting', 'languageValueUpdated', { resource: workspaceService.getWorkspace().folders[0].uri, overrideIdentifier: 'jsonc' }, ConfigurationTarget.WORKSPACE_FOLDER); + assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting', { resource: workspaceService.getWorkspace().folders[0].uri, overrideIdentifier: 'jsonc' }), 'languageValueUpdated'); + }); + + test('update resource language configuration for a language using configuration update overrides', async () => { + assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting', { resource: workspaceService.getWorkspace().folders[0].uri, overrideIdentifier: 'jsonc' }), 'languageValue'); + await testObject.updateValue('configurationService.folder.languageSetting', 'languageValueUpdated', { resource: workspaceService.getWorkspace().folders[0].uri, overrideIdentifiers: ['jsonc'] }, ConfigurationTarget.WORKSPACE_FOLDER); + assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting', { resource: workspaceService.getWorkspace().folders[0].uri, overrideIdentifier: 'jsonc' }), 'languageValueUpdated'); }); test('update application setting into workspace configuration in a workspace is not supported', () => { @@ -1058,12 +1087,6 @@ suite('WorkspaceConfigurationService - Folder', () => { .then(() => assert.ok(target.called)); }); - test('resource language configuration', async () => { - assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting', { resource: workspaceService.getWorkspace().folders[0].uri, overrideIdentifier: 'jsonc' }), 'languageValue'); - await testObject.updateValue('configurationService.folder.languageSetting', 'languageValueUpdated', { resource: workspaceService.getWorkspace().folders[0].uri, overrideIdentifier: 'jsonc' }, ConfigurationTarget.WORKSPACE_FOLDER); - assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting', { resource: workspaceService.getWorkspace().folders[0].uri, overrideIdentifier: 'jsonc' }), 'languageValueUpdated'); - }); - test('remove setting from all targets', async () => { const key = 'configurationService.folder.testSetting'; await testObject.updateValue(key, 'workspaceValue', ConfigurationTarget.WORKSPACE); diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index d82b437a7a2..bc46eaf51eb 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -47,13 +47,13 @@ export class ExtensionService extends AbstractExtensionService implements IExten @IExtensionManagementService extensionManagementService: IExtensionManagementService, @IWorkspaceContextService contextService: IWorkspaceContextService, @IConfigurationService configurationService: IConfigurationService, + @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, + @IWebExtensionsScannerService webExtensionsScannerService: IWebExtensionsScannerService, + @ILogService logService: ILogService, @IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, - @IWebExtensionsScannerService webExtensionsScannerService: IWebExtensionsScannerService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, - @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, @IUserDataInitializationService private readonly _userDataInitializationService: IUserDataInitializationService, - @ILogService private readonly _logService: ILogService, ) { super( instantiationService, @@ -67,7 +67,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten contextService, configurationService, extensionManifestPropertiesService, - webExtensionsScannerService + webExtensionsScannerService, + logService ); this._runningLocation = new Map(); diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts index 1a8d82ad570..a3ac30b427e 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts @@ -92,30 +92,23 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost } const forceHTTPS = (location.protocol === 'https:'); + const webEndpointUrlTemplate = this._productService.webEndpointUrlTemplate; + const commit = this._productService.commit; + const quality = this._productService.quality; + if (webEndpointUrlTemplate && commit && quality) { + const baseUrl = ( + webEndpointUrlTemplate + .replace('{{uuid}}', generateUuid()) + .replace('{{commit}}', commit) + .replace('{{quality}}', quality) + ); + const base = ( + forceHTTPS + ? `${baseUrl}/out/vs/workbench/services/extensions/worker/httpsWebWorkerExtensionHostIframe.html` + : `${baseUrl}/out/vs/workbench/services/extensions/worker/httpWebWorkerExtensionHostIframe.html` + ); - let uniqueWebWorkerExtensionHostOrigin = true; - if (this._environmentService.options && typeof this._environmentService.options.__uniqueWebWorkerExtensionHostOrigin !== 'undefined') { - uniqueWebWorkerExtensionHostOrigin = this._environmentService.options.__uniqueWebWorkerExtensionHostOrigin; - } - if (uniqueWebWorkerExtensionHostOrigin) { - const webEndpointUrlTemplate = this._productService.webEndpointUrlTemplate; - const commit = this._productService.commit; - const quality = this._productService.quality; - if (webEndpointUrlTemplate && commit && quality) { - const baseUrl = ( - webEndpointUrlTemplate - .replace('{{uuid}}', generateUuid()) - .replace('{{commit}}', commit) - .replace('{{quality}}', quality) - ); - const base = ( - forceHTTPS - ? `${baseUrl}/out/vs/workbench/services/extensions/worker/httpsWebWorkerExtensionHostIframe.html` - : `${baseUrl}/out/vs/workbench/services/extensions/worker/httpWebWorkerExtensionHostIframe.html` - ); - - return base + suffix; - } + return base + suffix; } if (this._productService.webEndpointUrl) { diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index 71764d5a156..cf9b2bffa77 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -183,6 +183,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx @IConfigurationService protected readonly _configurationService: IConfigurationService, @IExtensionManifestPropertiesService protected readonly _extensionManifestPropertiesService: IExtensionManifestPropertiesService, @IWebExtensionsScannerService protected readonly _webExtensionsScannerService: IWebExtensionsScannerService, + @ILogService protected readonly _logService: ILogService, ) { super(); @@ -495,6 +496,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx const workspace = await this._contextService.getCompleteWorkspace(); const forceUsingSearch = !!this._environmentService.remoteAuthority; const host: IWorkspaceContainsActivationHost = { + logService: this._logService, folders: workspace.folders.map(folder => folder.uri), forceUsingSearch: forceUsingSearch, exists: (uri) => this._fileService.exists(uri), diff --git a/src/vs/workbench/services/extensions/common/extensionHostMain.ts b/src/vs/workbench/services/extensions/common/extensionHostMain.ts index 288d3451fb1..4570e1d0ae1 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostMain.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostMain.ts @@ -64,8 +64,8 @@ export class ExtensionHostMain { const instaService: IInstantiationService = new InstantiationService(services, true); // ugly self - inject - const terminalService = instaService.invokeFunction(accessor => accessor.get(IExtHostTerminalService)); - this._disposables.add(terminalService); + this._disposables.add(instaService.invokeFunction(accessor => accessor.get(IExtHostTerminalService))); + this._disposables.add(instaService.invokeFunction(accessor => accessor.get(IExtHostExtensionService))); this._logService = instaService.invokeFunction(accessor => accessor.get(ILogService)); diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index 912c8b55818..66fc91a775c 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -134,19 +134,16 @@ export interface IExtensionHost { dispose(): void; } -export function isProposedApiEnabled(extension: IExtensionDescription, proposal?: ApiProposalName): boolean { - if (!proposal) { - return Boolean(extension.enableProposedApi); - } +export function isProposedApiEnabled(extension: IExtensionDescription, proposal: ApiProposalName): boolean { if (extension.enabledApiProposals?.includes(proposal)) { return true; } return Boolean(extension.enableProposedApi); } -export function checkProposedApiEnabled(extension: IExtensionDescription, proposal?: ApiProposalName): void { +export function checkProposedApiEnabled(extension: IExtensionDescription, proposal: ApiProposalName): void { if (!isProposedApiEnabled(extension, proposal)) { - throw new Error(`[${extension.identifier.value}]: Proposed API is only available when running out of dev or with the following command line switch: --enable-proposed-api ${extension.identifier.value}`); + throw new Error(`Extension '${extension.identifier.value}' CANNOT use API proposal: ${proposal}.\nIts package.json#enabledApiProposals-property declares: ${extension.enabledApiProposals?.join(', ') ?? '[]'} but NOT ${proposal}.\n The missing proposal MUST be added and you must start in extension development mode or use the following command line switch: --enable-proposed-api ${extension.identifier.value}`); } } diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 801927b4506..d413ce4beab 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -6,8 +6,59 @@ // THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY. export const allApiProposals = Object.freeze({ + authSession: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authSession.d.ts', + contribIconFonts: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribIconFonts.d.ts', + contribIcons: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribIcons.d.ts', + contribLabelFormatterWorkspaceTooltip: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribLabelFormatterWorkspaceTooltip.d.ts', + contribMenuBarHome: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMenuBarHome.d.ts', + contribRemoteHelp: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribRemoteHelp.d.ts', + contribViewsRemote: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewsRemote.d.ts', + contribViewsWelcome: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewsWelcome.d.ts', + customEditorMove: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.customEditorMove.d.ts', + diffCommand: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.diffCommand.d.ts', + documentFiltersExclusive: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.documentFiltersExclusive.d.ts', editorInsets: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editorInsets.d.ts', + extensionRuntime: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.extensionRuntime.d.ts', + externalUriOpener: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.externalUriOpener.d.ts', + fileSearchProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fileSearchProvider.d.ts', + findTextInFiles: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findTextInFiles.d.ts', fsChunks: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fsChunks.d.ts', - languageStatus: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageStatus.d.ts' + inlayHints: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlayHints.d.ts', + inlineCompletions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts', + languageStatus: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageStatus.d.ts', + notebookCellExecutionState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookCellExecutionState.d.ts', + notebookConcatTextDocument: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookConcatTextDocument.d.ts', + notebookContentProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookContentProvider.d.ts', + notebookControllerKind: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookControllerKind.d.ts', + notebookDebugOptions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookDebugOptions.d.ts', + notebookDeprecated: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookDeprecated.d.ts', + notebookEditor: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookEditor.d.ts', + notebookEditorDecorationType: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookEditorDecorationType.d.ts', + notebookEditorEdit: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookEditorEdit.d.ts', + notebookLiveShare: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookLiveShare.d.ts', + notebookMessaging: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookMessaging.d.ts', + notebookMime: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookMime.d.ts', + portsAttributes: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.portsAttributes.d.ts', + quickPickSeparators: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickSeparators.d.ts', + quickPickSortByLabel: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts', + resolvers: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.resolvers.d.ts', + scmActionButton: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmActionButton.d.ts', + scmSelectedProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts', + scmValidation: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmValidation.d.ts', + tabs: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tabs.d.ts', + taskPresentationGroup: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.taskPresentationGroup.d.ts', + terminalDataWriteEvent: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDataWriteEvent.d.ts', + terminalDimensions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDimensions.d.ts', + terminalLocation: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalLocation.d.ts', + terminalNameChangeEvent: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalNameChangeEvent.d.ts', + testCoverage: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testCoverage.d.ts', + testObserver: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testObserver.d.ts', + textDocumentNotebook: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textDocumentNotebook.d.ts', + textSearchProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts', + timeline: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.timeline.d.ts', + tokenInformation: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tokenInformation.d.ts', + treeViewDragAndDrop: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewDragAndDrop.d.ts', + treeViewReveal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewReveal.d.ts', + workspaceTrust: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.workspaceTrust.d.ts' }); export type ApiProposalName = keyof typeof allApiProposals; diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index a8e316ea1a1..3da31508314 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -57,7 +57,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten constructor( @IInstantiationService instantiationService: IInstantiationService, @INotificationService notificationService: INotificationService, - @IWorkbenchEnvironmentService _environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @ITelemetryService telemetryService: ITelemetryService, @IWorkbenchExtensionEnablementService extensionEnablementService: IWorkbenchExtensionEnablementService, @IFileService fileService: IFileService, @@ -65,22 +65,22 @@ export class ExtensionService extends AbstractExtensionService implements IExten @IExtensionManagementService extensionManagementService: IExtensionManagementService, @IWorkspaceContextService contextService: IWorkspaceContextService, @IConfigurationService configurationService: IConfigurationService, + @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, + @IWebExtensionsScannerService webExtensionsScannerService: IWebExtensionsScannerService, + @ILogService logService: ILogService, @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, @IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, - @IWebExtensionsScannerService webExtensionsScannerService: IWebExtensionsScannerService, @INativeHostService private readonly _nativeHostService: INativeHostService, @IHostService private readonly _hostService: IHostService, @IRemoteExplorerService private readonly _remoteExplorerService: IRemoteExplorerService, @IExtensionGalleryService private readonly _extensionGalleryService: IExtensionGalleryService, - @ILogService private readonly _logService: ILogService, @IWorkspaceTrustManagementService private readonly _workspaceTrustManagementService: IWorkspaceTrustManagementService, - @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, ) { super( instantiationService, notificationService, - _environmentService, + environmentService, telemetryService, extensionEnablementService, fileService, @@ -89,7 +89,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten contextService, configurationService, extensionManifestPropertiesService, - webExtensionsScannerService + webExtensionsScannerService, + logService ); [this._enableLocalWebWorker, this._lazyLocalWebWorker] = this._isLocalWebWorkerEnabled(); @@ -336,10 +337,10 @@ export class ExtensionService extends AbstractExtensionService implements IExten this._logService.info(`Invoking resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)})`); try { const result = await localProcessExtensionHost.resolveAuthority(remoteAuthority); - this._logService.info(`resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)}) returned '${result.authority.host}:${result.authority.port}' after ${sw.elapsed} ms`); + this._logService.info(`resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)}) returned '${result.authority.host}:${result.authority.port}' after ${sw.elapsed()} ms`); this._remoteAuthorityResolverService._setResolvedAuthority(result.authority, result.options); } catch (err) { - this._logService.error(`resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)}) returned an error after ${sw.elapsed} ms`, err); + this._logService.error(`resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)}) returned an error after ${sw.elapsed()} ms`, err); this._remoteAuthorityResolverService._setResolvedAuthorityError(remoteAuthority, err); } } @@ -374,9 +375,9 @@ export class ExtensionService extends AbstractExtensionService implements IExten this._logService.info(`Invoking resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)})`); try { resolverResult = await localProcessExtensionHost.resolveAuthority(remoteAuthority); - this._logService.info(`resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)}) returned '${resolverResult.authority.host}:${resolverResult.authority.port}' after ${sw.elapsed} ms`); + this._logService.info(`resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)}) returned '${resolverResult.authority.host}:${resolverResult.authority.port}' after ${sw.elapsed()} ms`); } catch (err) { - this._logService.error(`resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)}) returned an error after ${sw.elapsed} ms`, err); + this._logService.error(`resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)}) returned an error after ${sw.elapsed()} ms`, err); if (RemoteAuthorityResolverError.isNoResolverFound(err)) { err.isHandled = await this._handleNoResolverFound(remoteAuthority); } else { diff --git a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts index 777123794b1..7c141c4a42d 100644 --- a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts @@ -47,7 +47,6 @@ import { join } from 'vs/base/common/path'; import { IShellEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/shellEnvironmentService'; import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter'; import { SerializedError } from 'vs/base/common/errors'; -import { StopWatch } from 'vs/base/common/stopwatch'; import { removeDangerousEnvVariables } from 'vs/base/node/processes'; export interface ILocalProcessExtensionHostInitData { @@ -197,30 +196,18 @@ export class LocalProcessExtensionHost implements IExtensionHost { this.terminate(); } - private async _createExtensionHost(): Promise<{ id: string; }> { - const sw = new StopWatch(false); - const result = await this._extensionHostStarter.createExtensionHost(); - if (sw.elapsed() > 20) { - // communicating to the shared process took more than 20ms - this._logService.info(`[LocalProcessExtensionHost]: IExtensionHostStarter.createExtensionHost() took ${sw.elapsed()} ms.`); - } - return result; - } - public start(): Promise | null { if (this._terminating) { // .terminate() was called return null; } - const timer = new LocalProcessExtensionHostStartupTimer(); - if (!this._messageProtocol) { this._messageProtocol = Promise.all([ - spyPromise(this._createExtensionHost(), () => timer.markDidCreateExtensionHost()), - spyPromise(this._tryListenOnPipe(), () => timer.markDidListenOnPipe()), - spyPromise(this._tryFindDebugPort(), () => timer.markDidFindDebugPort()), - spyPromise(this._shellEnvironmentService.getShellEnv(), () => timer.markDidGetShellEnv()), + this._extensionHostStarter.createExtensionHost(), + this._tryListenOnPipe(), + this._tryFindDebugPort(), + this._shellEnvironmentService.getShellEnv(), ]).then(([extensionHostCreationResult, pipeName, portNumber, processEnv]) => { this._extensionHostProcess = new ExtensionHostProcess(extensionHostCreationResult.id, this._extensionHostStarter); @@ -379,12 +366,7 @@ export class LocalProcessExtensionHost implements IExtensionHost { } // Initialize extension host process with hand shakes - return this._tryExtHostHandshake(opts, timer).then((protocol) => { - timer.markDidFinishHandhsake(); - - const localProcessExtensionHostStartupTimesEvent = timer.toEvent(); - this._telemetryService.publicLog2('localProcessExtensionHostStartupTimes', localProcessExtensionHostStartupTimesEvent); - + return this._tryExtHostHandshake(opts).then((protocol) => { clearTimeout(startupTimeoutHandle); return protocol; }); @@ -442,7 +424,7 @@ export class LocalProcessExtensionHost implements IExtensionHost { return port || 0; } - private _tryExtHostHandshake(opts: IExtensionHostProcessOptions, timer: LocalProcessExtensionHostStartupTimer): Promise { + private _tryExtHostHandshake(opts: IExtensionHostProcessOptions): Promise { return new Promise((resolve, reject) => { @@ -457,7 +439,6 @@ export class LocalProcessExtensionHost implements IExtensionHost { }, 60 * 1000); this._namedPipeServer!.on('connection', socket => { - timer.markDidReceiveConnection(); clearTimeout(handle); if (this._namedPipeServer) { @@ -473,12 +454,7 @@ export class LocalProcessExtensionHost implements IExtensionHost { }); // Now that the named pipe listener is installed, start the ext host process - const sw = new StopWatch(false); this._extensionHostProcess!.start(opts).then(() => { - sw.stop(); - timer.markDidStartExtensionHost(); - - this._logService.info(`[LocalProcessExtensionHost]: IExtensionHostStarter.start() took ${sw.elapsed()} ms.`); }, (err) => { // Starting the ext host process resulted in an error reject(err); @@ -506,7 +482,6 @@ export class LocalProcessExtensionHost implements IExtensionHost { const disposable = protocol.onMessage(msg => { if (isMessageOfType(msg, MessageType.Ready)) { - timer.markDidReceiveReady(); // 1) Extension Host is ready to receive messages, initialize it uninstallTimeoutCheck(); @@ -522,7 +497,6 @@ export class LocalProcessExtensionHost implements IExtensionHost { } if (isMessageOfType(msg, MessageType.Initialized)) { - timer.markDidReceiveInitialized(); // 2) Extension Host is initialized uninstallTimeoutCheck(); @@ -743,100 +717,3 @@ export class LocalProcessExtensionHost implements IExtensionHost { } } } - -async function spyPromise(p: Promise, whenDone: () => void): Promise { - const result = await p; - whenDone(); - return result; -} - -type LocalProcessExtensionHostStartupTimesClassification = { - didCreateExtensionHost: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true }; - didListenOnPipe: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true }; - didFindDebugPort: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true }; - didGetShellEnv: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true }; - didStartExtensionHost: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true }; - didReceiveConnection: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true }; - didReceiveReady: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true }; - didReceiveInitialized: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true }; - didFinishHandhsake: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true }; -}; -type LocalProcessExtensionHostStartupTimesEvent = { - didCreateExtensionHost: number; - didListenOnPipe: number; - didFindDebugPort: number; - didGetShellEnv: number; - didStartExtensionHost: number; - didReceiveConnection: number; - didReceiveReady: number; - didReceiveInitialized: number; - didFinishHandhsake: number; -}; - -class LocalProcessExtensionHostStartupTimer { - - private readonly _sw: StopWatch; - - constructor() { - this._sw = new StopWatch(false); - } - - public toEvent(): LocalProcessExtensionHostStartupTimesEvent { - return { - didCreateExtensionHost: this.didCreateExtensionHost, - didListenOnPipe: this.didListenOnPipe, - didFindDebugPort: this.didFindDebugPort, - didGetShellEnv: this.didGetShellEnv, - didStartExtensionHost: this.didStartExtensionHost, - didReceiveConnection: this.didReceiveConnection, - didReceiveReady: this.didReceiveReady, - didReceiveInitialized: this.didReceiveInitialized, - didFinishHandhsake: this.didFinishHandhsake, - }; - } - - private didCreateExtensionHost = 0; - public markDidCreateExtensionHost() { - this.didCreateExtensionHost = this._sw.elapsed(); - } - - private didListenOnPipe = 0; - public markDidListenOnPipe() { - this.didListenOnPipe = this._sw.elapsed(); - } - - private didFindDebugPort = 0; - public markDidFindDebugPort() { - this.didFindDebugPort = this._sw.elapsed(); - } - - private didGetShellEnv = 0; - public markDidGetShellEnv() { - this.didGetShellEnv = this._sw.elapsed(); - } - - private didStartExtensionHost = 0; - public markDidStartExtensionHost() { - this.didStartExtensionHost = this._sw.elapsed(); - } - - private didReceiveConnection = 0; - public markDidReceiveConnection() { - this.didReceiveConnection = this._sw.elapsed(); - } - - private didReceiveReady = 0; - public markDidReceiveReady() { - this.didReceiveReady = this._sw.elapsed(); - } - - private didReceiveInitialized = 0; - public markDidReceiveInitialized() { - this.didReceiveInitialized = this._sw.elapsed(); - } - - private didFinishHandhsake = 0; - public markDidFinishHandhsake() { - this.didFinishHandhsake = this._sw.elapsed(); - } -} diff --git a/src/vs/workbench/services/extensions/electron-sandbox/extensionHostStarter.ts b/src/vs/workbench/services/extensions/electron-sandbox/extensionHostStarter.ts index 9bdffcd67fd..b2ea5b06d91 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/extensionHostStarter.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/extensionHostStarter.ts @@ -3,13 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { registerMainProcessRemoteService, registerSharedProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services'; +import { registerMainProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services'; import { IExtensionHostStarter, ipcExtensionHostStarterChannelName } from 'vs/platform/extensions/common/extensionHostStarter'; -const location = 'main' as 'main' | 'shared'; - -if (location === 'main') { - registerMainProcessRemoteService(IExtensionHostStarter, ipcExtensionHostStarterChannelName, { supportsDelayedInstantiation: true }); -} else { - registerSharedProcessRemoteService(IExtensionHostStarter, ipcExtensionHostStarterChannelName, { supportsDelayedInstantiation: true }); -} +registerMainProcessRemoteService(IExtensionHostStarter, ipcExtensionHostStarterChannelName, { supportsDelayedInstantiation: true }); diff --git a/src/vs/workbench/services/extensions/node/proxyResolver.ts b/src/vs/workbench/services/extensions/node/proxyResolver.ts index 9c29c7d2b55..8b6e10d7ae1 100644 --- a/src/vs/workbench/services/extensions/node/proxyResolver.ts +++ b/src/vs/workbench/services/extensions/node/proxyResolver.ts @@ -15,7 +15,6 @@ import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { LogLevel, createHttpPatch, ProxyResolveEvent, createProxyResolver, createTlsPatch, ProxySupportSetting } from 'vscode-proxy-agent'; -import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; export function connectProxyResolver( extHostWorkspace: IExtHostWorkspaceProvider, @@ -130,9 +129,6 @@ function configureModuleLoading(extensionService: ExtHostExtensionService, looku } if (!cache[request]) { let mod = modules.default; - if (ext && isProposedApiEnabled(ext)) { - mod = (modules as any)[(ext).proxySupport] || modules.onRequest; - } cache[request] = { ...mod }; // Copy to work around #93167. } return cache[request]; diff --git a/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts b/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts index a9e065ae5ee..cc5a96cedfc 100644 --- a/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts +++ b/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts @@ -89,6 +89,11 @@ export class NativeHostService extends Disposable implements IHostService { } private doOpenEmptyWindow(options?: IOpenEmptyWindowOptions): Promise { + const remoteAuthority = this.environmentService.remoteAuthority; + if (!!remoteAuthority && options?.remoteAuthority === undefined) { + // set the remoteAuthority of the window the request came from + options = options ? { ...options, remoteAuthority } : { remoteAuthority }; + } return this.nativeHostService.openWindow(options); } diff --git a/src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts b/src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts index 988566b13e7..06df5b9e20a 100644 --- a/src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts +++ b/src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts @@ -1022,19 +1022,15 @@ export class MacLinuxKeyboardMapper implements IKeyboardMapper { private static _redirectCharCode(charCode: number): number { switch (charCode) { // allow-any-unicode-next-line - case CharCode.U_IDEOGRAPHIC_FULL_STOP: return CharCode.Period; // CJK 。 => . - // allow-any-unicode-next-line - case CharCode.U_LEFT_CORNER_BRACKET: return CharCode.OpenSquareBracket; // CJK 「 => [ - // allow-any-unicode-next-line - case CharCode.U_RIGHT_CORNER_BRACKET: return CharCode.CloseSquareBracket; // CJK 」 => ] - // allow-any-unicode-next-line - case CharCode.U_LEFT_BLACK_LENTICULAR_BRACKET: return CharCode.OpenSquareBracket; // CJK 【 => [ - // allow-any-unicode-next-line - case CharCode.U_RIGHT_BLACK_LENTICULAR_BRACKET: return CharCode.CloseSquareBracket; // CJK 】 => ] - // allow-any-unicode-next-line - case CharCode.U_FULLWIDTH_SEMICOLON: return CharCode.Semicolon; // CJK ; => ; - // allow-any-unicode-next-line - case CharCode.U_FULLWIDTH_COMMA: return CharCode.Comma; // CJK , => , + // CJK: 。 「 」 【 】 ; , + // map: . [ ] [ ] ; , + case CharCode.U_IDEOGRAPHIC_FULL_STOP: return CharCode.Period; + case CharCode.U_LEFT_CORNER_BRACKET: return CharCode.OpenSquareBracket; + case CharCode.U_RIGHT_CORNER_BRACKET: return CharCode.CloseSquareBracket; + case CharCode.U_LEFT_BLACK_LENTICULAR_BRACKET: return CharCode.OpenSquareBracket; + case CharCode.U_RIGHT_BLACK_LENTICULAR_BRACKET: return CharCode.CloseSquareBracket; + case CharCode.U_FULLWIDTH_SEMICOLON: return CharCode.Semicolon; + case CharCode.U_FULLWIDTH_COMMA: return CharCode.Comma; } return charCode; } diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/macLinuxFallbackKeyboardMapper.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/macLinuxFallbackKeyboardMapper.test.ts index 65c134b79ca..25dd4f57a33 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/macLinuxFallbackKeyboardMapper.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/macLinuxFallbackKeyboardMapper.test.ts @@ -161,7 +161,7 @@ suite('keyboardMapper - MAC fallback', () => { }, { label: '⌥', - ariaLabel: 'Alt', + ariaLabel: 'Option', electronAccelerator: null, userSettingsLabel: 'alt', isWYSIWYG: true, diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/macLinuxKeyboardMapper.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/macLinuxKeyboardMapper.test.ts index 4897953ef39..a5905bc8704 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/macLinuxKeyboardMapper.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/macLinuxKeyboardMapper.test.ts @@ -134,7 +134,7 @@ suite('keyboardMapper - MAC de_ch', () => { KeyMod.CtrlCmd | KeyCode.BracketRight, [{ label: '⌃⌥⌘6', - ariaLabel: 'Control+Alt+Command+6', + ariaLabel: 'Control+Option+Command+6', electronAccelerator: 'Ctrl+Alt+Cmd+6', userSettingsLabel: 'ctrl+alt+cmd+6', isWYSIWYG: true, @@ -175,7 +175,7 @@ suite('keyboardMapper - MAC de_ch', () => { KeyMod.Shift | KeyCode.BracketRight, [{ label: '⌃⌥9', - ariaLabel: 'Control+Alt+9', + ariaLabel: 'Control+Option+9', electronAccelerator: 'Ctrl+Alt+9', userSettingsLabel: 'ctrl+alt+9', isWYSIWYG: true, @@ -223,7 +223,7 @@ suite('keyboardMapper - MAC de_ch', () => { KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Backslash), [{ label: '⌘K ⌃⇧⌥⌘7', - ariaLabel: 'Command+K Control+Shift+Alt+Command+7', + ariaLabel: 'Command+K Control+Shift+Option+Command+7', electronAccelerator: null, userSettingsLabel: 'cmd+k ctrl+shift+alt+cmd+7', isWYSIWYG: true, diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/mac_de_ch.txt b/src/vs/workbench/services/keybinding/test/electron-browser/mac_de_ch.txt index 973d6a28603..60627a704dc 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/mac_de_ch.txt +++ b/src/vs/workbench/services/keybinding/test/electron-browser/mac_de_ch.txt @@ -6,37 +6,37 @@ isUSStandard: false | Ctrl+KeyA | a | Ctrl+A | | Ctrl+A | ctrl+a | Ctrl+A | ctrl+[KeyA] | | | Shift+KeyA | A | Shift+A | | Shift+A | shift+a | Shift+A | shift+[KeyA] | | | Ctrl+Shift+KeyA | A | Ctrl+Shift+A | | Ctrl+Shift+A | ctrl+shift+a | Ctrl+Shift+A | ctrl+shift+[KeyA] | | -| Alt+KeyA | a | Alt+A | | Alt+A | alt+a | Alt+A | alt+[KeyA] | | -| Ctrl+Alt+KeyA | å | Ctrl+Alt+A | | Ctrl+Alt+A | ctrl+alt+a | Ctrl+Alt+A | ctrl+alt+[KeyA] | | -| Shift+Alt+KeyA | A | Shift+Alt+A | | Shift+Alt+A | shift+alt+a | Shift+Alt+A | shift+alt+[KeyA] | | -| Ctrl+Shift+Alt+KeyA | Å | Ctrl+Shift+Alt+A | | Ctrl+Shift+Alt+A | ctrl+shift+alt+a | Ctrl+Shift+Alt+A | ctrl+shift+alt+[KeyA] | | +| Alt+KeyA | a | Alt+A | | Option+A | alt+a | Alt+A | alt+[KeyA] | | +| Ctrl+Alt+KeyA | å | Ctrl+Alt+A | | Ctrl+Option+A | ctrl+alt+a | Ctrl+Alt+A | ctrl+alt+[KeyA] | | +| Shift+Alt+KeyA | A | Shift+Alt+A | | Shift+Option+A | shift+alt+a | Shift+Alt+A | shift+alt+[KeyA] | | +| Ctrl+Shift+Alt+KeyA | Å | Ctrl+Shift+Alt+A | | Ctrl+Shift+Option+A | ctrl+shift+alt+a | Ctrl+Shift+Alt+A | ctrl+shift+alt+[KeyA] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyB | b | B | | B | b | B | [KeyB] | | | Ctrl+KeyB | b | Ctrl+B | | Ctrl+B | ctrl+b | Ctrl+B | ctrl+[KeyB] | | | Shift+KeyB | B | Shift+B | | Shift+B | shift+b | Shift+B | shift+[KeyB] | | | Ctrl+Shift+KeyB | B | Ctrl+Shift+B | | Ctrl+Shift+B | ctrl+shift+b | Ctrl+Shift+B | ctrl+shift+[KeyB] | | -| Alt+KeyB | b | Alt+B | | Alt+B | alt+b | Alt+B | alt+[KeyB] | | -| Ctrl+Alt+KeyB | ∫ | Ctrl+Alt+B | | Ctrl+Alt+B | ctrl+alt+b | Ctrl+Alt+B | ctrl+alt+[KeyB] | | -| Shift+Alt+KeyB | B | Shift+Alt+B | | Shift+Alt+B | shift+alt+b | Shift+Alt+B | shift+alt+[KeyB] | | -| Ctrl+Shift+Alt+KeyB | --- | Ctrl+Shift+Alt+B | | Ctrl+Shift+Alt+B | ctrl+shift+alt+b | Ctrl+Shift+Alt+B | ctrl+shift+alt+[KeyB] | | +| Alt+KeyB | b | Alt+B | | Option+B | alt+b | Alt+B | alt+[KeyB] | | +| Ctrl+Alt+KeyB | ∫ | Ctrl+Alt+B | | Ctrl+Option+B | ctrl+alt+b | Ctrl+Alt+B | ctrl+alt+[KeyB] | | +| Shift+Alt+KeyB | B | Shift+Alt+B | | Shift+Option+B | shift+alt+b | Shift+Alt+B | shift+alt+[KeyB] | | +| Ctrl+Shift+Alt+KeyB | --- | Ctrl+Shift+Alt+B | | Ctrl+Shift+Option+B | ctrl+shift+alt+b | Ctrl+Shift+Alt+B | ctrl+shift+alt+[KeyB] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyC | c | C | | C | c | C | [KeyC] | | | Ctrl+KeyC | c | Ctrl+C | | Ctrl+C | ctrl+c | Ctrl+C | ctrl+[KeyC] | | | Shift+KeyC | C | Shift+C | | Shift+C | shift+c | Shift+C | shift+[KeyC] | | | Ctrl+Shift+KeyC | C | Ctrl+Shift+C | | Ctrl+Shift+C | ctrl+shift+c | Ctrl+Shift+C | ctrl+shift+[KeyC] | | -| Alt+KeyC | c | Alt+C | | Alt+C | alt+c | Alt+C | alt+[KeyC] | | -| Ctrl+Alt+KeyC | © | Ctrl+Alt+C | | Ctrl+Alt+C | ctrl+alt+c | Ctrl+Alt+C | ctrl+alt+[KeyC] | | -| Shift+Alt+KeyC | C | Shift+Alt+C | | Shift+Alt+C | shift+alt+c | Shift+Alt+C | shift+alt+[KeyC] | | -| Ctrl+Shift+Alt+KeyC | --- | Ctrl+Shift+Alt+C | | Ctrl+Shift+Alt+C | ctrl+shift+alt+c | Ctrl+Shift+Alt+C | ctrl+shift+alt+[KeyC] | | +| Alt+KeyC | c | Alt+C | | Option+C | alt+c | Alt+C | alt+[KeyC] | | +| Ctrl+Alt+KeyC | © | Ctrl+Alt+C | | Ctrl+Option+C | ctrl+alt+c | Ctrl+Alt+C | ctrl+alt+[KeyC] | | +| Shift+Alt+KeyC | C | Shift+Alt+C | | Shift+Option+C | shift+alt+c | Shift+Alt+C | shift+alt+[KeyC] | | +| Ctrl+Shift+Alt+KeyC | --- | Ctrl+Shift+Alt+C | | Ctrl+Shift+Option+C | ctrl+shift+alt+c | Ctrl+Shift+Alt+C | ctrl+shift+alt+[KeyC] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyD | d | D | | D | d | D | [KeyD] | | | Ctrl+KeyD | d | Ctrl+D | | Ctrl+D | ctrl+d | Ctrl+D | ctrl+[KeyD] | | | Shift+KeyD | D | Shift+D | | Shift+D | shift+d | Shift+D | shift+[KeyD] | | | Ctrl+Shift+KeyD | D | Ctrl+Shift+D | | Ctrl+Shift+D | ctrl+shift+d | Ctrl+Shift+D | ctrl+shift+[KeyD] | | -| Alt+KeyD | d | Alt+D | | Alt+D | alt+d | Alt+D | alt+[KeyD] | | -| Ctrl+Alt+KeyD | ∂ | Ctrl+Alt+D | | Ctrl+Alt+D | ctrl+alt+d | Ctrl+Alt+D | ctrl+alt+[KeyD] | | -| Shift+Alt+KeyD | D | Shift+Alt+D | | Shift+Alt+D | shift+alt+d | Shift+Alt+D | shift+alt+[KeyD] | | -| Ctrl+Shift+Alt+KeyD | fl | Ctrl+Shift+Alt+D | | Ctrl+Shift+Alt+D | ctrl+shift+alt+d | Ctrl+Shift+Alt+D | ctrl+shift+alt+[KeyD] | | +| Alt+KeyD | d | Alt+D | | Option+D | alt+d | Alt+D | alt+[KeyD] | | +| Ctrl+Alt+KeyD | ∂ | Ctrl+Alt+D | | Ctrl+Option+D | ctrl+alt+d | Ctrl+Alt+D | ctrl+alt+[KeyD] | | +| Shift+Alt+KeyD | D | Shift+Alt+D | | Shift+Option+D | shift+alt+d | Shift+Alt+D | shift+alt+[KeyD] | | +| Ctrl+Shift+Alt+KeyD | fl | Ctrl+Shift+Alt+D | | Ctrl+Shift+Option+D | ctrl+shift+alt+d | Ctrl+Shift+Alt+D | ctrl+shift+alt+[KeyD] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -44,37 +44,37 @@ isUSStandard: false | Ctrl+KeyE | e | Ctrl+E | | Ctrl+E | ctrl+e | Ctrl+E | ctrl+[KeyE] | | | Shift+KeyE | E | Shift+E | | Shift+E | shift+e | Shift+E | shift+[KeyE] | | | Ctrl+Shift+KeyE | E | Ctrl+Shift+E | | Ctrl+Shift+E | ctrl+shift+e | Ctrl+Shift+E | ctrl+shift+[KeyE] | | -| Alt+KeyE | e | Alt+E | | Alt+E | alt+e | Alt+E | alt+[KeyE] | | -| Ctrl+Alt+KeyE | € | Ctrl+Alt+E | | Ctrl+Alt+E | ctrl+alt+e | Ctrl+Alt+E | ctrl+alt+[KeyE] | | -| Shift+Alt+KeyE | E | Shift+Alt+E | | Shift+Alt+E | shift+alt+e | Shift+Alt+E | shift+alt+[KeyE] | | -| Ctrl+Shift+Alt+KeyE | Ë | Ctrl+Shift+Alt+E | | Ctrl+Shift+Alt+E | ctrl+shift+alt+e | Ctrl+Shift+Alt+E | ctrl+shift+alt+[KeyE] | | +| Alt+KeyE | e | Alt+E | | Option+E | alt+e | Alt+E | alt+[KeyE] | | +| Ctrl+Alt+KeyE | € | Ctrl+Alt+E | | Ctrl+Option+E | ctrl+alt+e | Ctrl+Alt+E | ctrl+alt+[KeyE] | | +| Shift+Alt+KeyE | E | Shift+Alt+E | | Shift+Option+E | shift+alt+e | Shift+Alt+E | shift+alt+[KeyE] | | +| Ctrl+Shift+Alt+KeyE | Ë | Ctrl+Shift+Alt+E | | Ctrl+Shift+Option+E | ctrl+shift+alt+e | Ctrl+Shift+Alt+E | ctrl+shift+alt+[KeyE] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyF | f | F | | F | f | F | [KeyF] | | | Ctrl+KeyF | f | Ctrl+F | | Ctrl+F | ctrl+f | Ctrl+F | ctrl+[KeyF] | | | Shift+KeyF | F | Shift+F | | Shift+F | shift+f | Shift+F | shift+[KeyF] | | | Ctrl+Shift+KeyF | F | Ctrl+Shift+F | | Ctrl+Shift+F | ctrl+shift+f | Ctrl+Shift+F | ctrl+shift+[KeyF] | | -| Alt+KeyF | f | Alt+F | | Alt+F | alt+f | Alt+F | alt+[KeyF] | | -| Ctrl+Alt+KeyF | ƒ | Ctrl+Alt+F | | Ctrl+Alt+F | ctrl+alt+f | Ctrl+Alt+F | ctrl+alt+[KeyF] | | -| Shift+Alt+KeyF | F | Shift+Alt+F | | Shift+Alt+F | shift+alt+f | Shift+Alt+F | shift+alt+[KeyF] | | -| Ctrl+Shift+Alt+KeyF | ‡ | Ctrl+Shift+Alt+F | | Ctrl+Shift+Alt+F | ctrl+shift+alt+f | Ctrl+Shift+Alt+F | ctrl+shift+alt+[KeyF] | | +| Alt+KeyF | f | Alt+F | | Option+F | alt+f | Alt+F | alt+[KeyF] | | +| Ctrl+Alt+KeyF | ƒ | Ctrl+Alt+F | | Ctrl+Option+F | ctrl+alt+f | Ctrl+Alt+F | ctrl+alt+[KeyF] | | +| Shift+Alt+KeyF | F | Shift+Alt+F | | Shift+Option+F | shift+alt+f | Shift+Alt+F | shift+alt+[KeyF] | | +| Ctrl+Shift+Alt+KeyF | ‡ | Ctrl+Shift+Alt+F | | Ctrl+Shift+Option+F | ctrl+shift+alt+f | Ctrl+Shift+Alt+F | ctrl+shift+alt+[KeyF] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyG | g | G | | G | g | G | [KeyG] | | | Ctrl+KeyG | g | Ctrl+G | | Ctrl+G | ctrl+g | Ctrl+G | ctrl+[KeyG] | | | Shift+KeyG | G | Shift+G | | Shift+G | shift+g | Shift+G | shift+[KeyG] | | | Ctrl+Shift+KeyG | G | Ctrl+Shift+G | | Ctrl+Shift+G | ctrl+shift+g | Ctrl+Shift+G | ctrl+shift+[KeyG] | | -| Alt+KeyG | g | Alt+G | | Alt+G | alt+g | Alt+G | alt+[KeyG] | | -| Ctrl+Alt+KeyG | @ | Ctrl+Alt+G | | Ctrl+Alt+G | ctrl+alt+g | Ctrl+Alt+G | ctrl+alt+[KeyG] | | -| Shift+Alt+KeyG | G | Shift+Alt+G | | Shift+Alt+G | shift+alt+g | Shift+Alt+G | shift+alt+[KeyG] | | -| Ctrl+Shift+Alt+KeyG | ‚ | Ctrl+Shift+Alt+G | | Ctrl+Shift+Alt+G | ctrl+shift+alt+g | Ctrl+Shift+Alt+G | ctrl+shift+alt+[KeyG] | | +| Alt+KeyG | g | Alt+G | | Option+G | alt+g | Alt+G | alt+[KeyG] | | +| Ctrl+Alt+KeyG | @ | Ctrl+Alt+G | | Ctrl+Option+G | ctrl+alt+g | Ctrl+Alt+G | ctrl+alt+[KeyG] | | +| Shift+Alt+KeyG | G | Shift+Alt+G | | Shift+Option+G | shift+alt+g | Shift+Alt+G | shift+alt+[KeyG] | | +| Ctrl+Shift+Alt+KeyG | ‚ | Ctrl+Shift+Alt+G | | Ctrl+Shift+Option+G | ctrl+shift+alt+g | Ctrl+Shift+Alt+G | ctrl+shift+alt+[KeyG] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyH | h | H | | H | h | H | [KeyH] | | | Ctrl+KeyH | h | Ctrl+H | | Ctrl+H | ctrl+h | Ctrl+H | ctrl+[KeyH] | | | Shift+KeyH | H | Shift+H | | Shift+H | shift+h | Shift+H | shift+[KeyH] | | | Ctrl+Shift+KeyH | H | Ctrl+Shift+H | | Ctrl+Shift+H | ctrl+shift+h | Ctrl+Shift+H | ctrl+shift+[KeyH] | | -| Alt+KeyH | h | Alt+H | | Alt+H | alt+h | Alt+H | alt+[KeyH] | | -| Ctrl+Alt+KeyH | ª | Ctrl+Alt+H | | Ctrl+Alt+H | ctrl+alt+h | Ctrl+Alt+H | ctrl+alt+[KeyH] | | -| Shift+Alt+KeyH | H | Shift+Alt+H | | Shift+Alt+H | shift+alt+h | Shift+Alt+H | shift+alt+[KeyH] | | -| Ctrl+Shift+Alt+KeyH | · | Ctrl+Shift+Alt+H | | Ctrl+Shift+Alt+H | ctrl+shift+alt+h | Ctrl+Shift+Alt+H | ctrl+shift+alt+[KeyH] | | +| Alt+KeyH | h | Alt+H | | Option+H | alt+h | Alt+H | alt+[KeyH] | | +| Ctrl+Alt+KeyH | ª | Ctrl+Alt+H | | Ctrl+Option+H | ctrl+alt+h | Ctrl+Alt+H | ctrl+alt+[KeyH] | | +| Shift+Alt+KeyH | H | Shift+Alt+H | | Shift+Option+H | shift+alt+h | Shift+Alt+H | shift+alt+[KeyH] | | +| Ctrl+Shift+Alt+KeyH | · | Ctrl+Shift+Alt+H | | Ctrl+Shift+Option+H | ctrl+shift+alt+h | Ctrl+Shift+Alt+H | ctrl+shift+alt+[KeyH] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -82,37 +82,37 @@ isUSStandard: false | Ctrl+KeyI | i | Ctrl+I | | Ctrl+I | ctrl+i | Ctrl+I | ctrl+[KeyI] | | | Shift+KeyI | I | Shift+I | | Shift+I | shift+i | Shift+I | shift+[KeyI] | | | Ctrl+Shift+KeyI | I | Ctrl+Shift+I | | Ctrl+Shift+I | ctrl+shift+i | Ctrl+Shift+I | ctrl+shift+[KeyI] | | -| Alt+KeyI | i | Alt+I | | Alt+I | alt+i | Alt+I | alt+[KeyI] | | -| Ctrl+Alt+KeyI | ¡ | Ctrl+Alt+I | | Ctrl+Alt+I | ctrl+alt+i | Ctrl+Alt+I | ctrl+alt+[KeyI] | | -| Shift+Alt+KeyI | I | Shift+Alt+I | | Shift+Alt+I | shift+alt+i | Shift+Alt+I | shift+alt+[KeyI] | | -| Ctrl+Shift+Alt+KeyI | ı | Ctrl+Shift+Alt+I | | Ctrl+Shift+Alt+I | ctrl+shift+alt+i | Ctrl+Shift+Alt+I | ctrl+shift+alt+[KeyI] | | +| Alt+KeyI | i | Alt+I | | Option+I | alt+i | Alt+I | alt+[KeyI] | | +| Ctrl+Alt+KeyI | ¡ | Ctrl+Alt+I | | Ctrl+Option+I | ctrl+alt+i | Ctrl+Alt+I | ctrl+alt+[KeyI] | | +| Shift+Alt+KeyI | I | Shift+Alt+I | | Shift+Option+I | shift+alt+i | Shift+Alt+I | shift+alt+[KeyI] | | +| Ctrl+Shift+Alt+KeyI | ı | Ctrl+Shift+Alt+I | | Ctrl+Shift+Option+I | ctrl+shift+alt+i | Ctrl+Shift+Alt+I | ctrl+shift+alt+[KeyI] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyJ | j | J | | J | j | J | [KeyJ] | | | Ctrl+KeyJ | j | Ctrl+J | | Ctrl+J | ctrl+j | Ctrl+J | ctrl+[KeyJ] | | | Shift+KeyJ | J | Shift+J | | Shift+J | shift+j | Shift+J | shift+[KeyJ] | | | Ctrl+Shift+KeyJ | J | Ctrl+Shift+J | | Ctrl+Shift+J | ctrl+shift+j | Ctrl+Shift+J | ctrl+shift+[KeyJ] | | -| Alt+KeyJ | j | Alt+J | | Alt+J | alt+j | Alt+J | alt+[KeyJ] | | -| Ctrl+Alt+KeyJ | º | Ctrl+Alt+J | | Ctrl+Alt+J | ctrl+alt+j | Ctrl+Alt+J | ctrl+alt+[KeyJ] | | -| Shift+Alt+KeyJ | J | Shift+Alt+J | | Shift+Alt+J | shift+alt+j | Shift+Alt+J | shift+alt+[KeyJ] | | -| Ctrl+Shift+Alt+KeyJ | ˜ | Ctrl+Shift+Alt+J | | Ctrl+Shift+Alt+J | ctrl+shift+alt+j | Ctrl+Shift+Alt+J | ctrl+shift+alt+[KeyJ] | | +| Alt+KeyJ | j | Alt+J | | Option+J | alt+j | Alt+J | alt+[KeyJ] | | +| Ctrl+Alt+KeyJ | º | Ctrl+Alt+J | | Ctrl+Option+J | ctrl+alt+j | Ctrl+Alt+J | ctrl+alt+[KeyJ] | | +| Shift+Alt+KeyJ | J | Shift+Alt+J | | Shift+Option+J | shift+alt+j | Shift+Alt+J | shift+alt+[KeyJ] | | +| Ctrl+Shift+Alt+KeyJ | ˜ | Ctrl+Shift+Alt+J | | Ctrl+Shift+Option+J | ctrl+shift+alt+j | Ctrl+Shift+Alt+J | ctrl+shift+alt+[KeyJ] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyK | k | K | | K | k | K | [KeyK] | | | Ctrl+KeyK | k | Ctrl+K | | Ctrl+K | ctrl+k | Ctrl+K | ctrl+[KeyK] | | | Shift+KeyK | K | Shift+K | | Shift+K | shift+k | Shift+K | shift+[KeyK] | | | Ctrl+Shift+KeyK | K | Ctrl+Shift+K | | Ctrl+Shift+K | ctrl+shift+k | Ctrl+Shift+K | ctrl+shift+[KeyK] | | -| Alt+KeyK | k | Alt+K | | Alt+K | alt+k | Alt+K | alt+[KeyK] | | -| Ctrl+Alt+KeyK | ∆ | Ctrl+Alt+K | | Ctrl+Alt+K | ctrl+alt+k | Ctrl+Alt+K | ctrl+alt+[KeyK] | | -| Shift+Alt+KeyK | K | Shift+Alt+K | | Shift+Alt+K | shift+alt+k | Shift+Alt+K | shift+alt+[KeyK] | | -| Ctrl+Shift+Alt+KeyK | ¯ | Ctrl+Shift+Alt+K | | Ctrl+Shift+Alt+K | ctrl+shift+alt+k | Ctrl+Shift+Alt+K | ctrl+shift+alt+[KeyK] | | +| Alt+KeyK | k | Alt+K | | Option+K | alt+k | Alt+K | alt+[KeyK] | | +| Ctrl+Alt+KeyK | ∆ | Ctrl+Alt+K | | Ctrl+Option+K | ctrl+alt+k | Ctrl+Alt+K | ctrl+alt+[KeyK] | | +| Shift+Alt+KeyK | K | Shift+Alt+K | | Shift+Option+K | shift+alt+k | Shift+Alt+K | shift+alt+[KeyK] | | +| Ctrl+Shift+Alt+KeyK | ¯ | Ctrl+Shift+Alt+K | | Ctrl+Shift+Option+K | ctrl+shift+alt+k | Ctrl+Shift+Alt+K | ctrl+shift+alt+[KeyK] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyL | l | L | | L | l | L | [KeyL] | | | Ctrl+KeyL | l | Ctrl+L | | Ctrl+L | ctrl+l | Ctrl+L | ctrl+[KeyL] | | | Shift+KeyL | L | Shift+L | | Shift+L | shift+l | Shift+L | shift+[KeyL] | | | Ctrl+Shift+KeyL | L | Ctrl+Shift+L | | Ctrl+Shift+L | ctrl+shift+l | Ctrl+Shift+L | ctrl+shift+[KeyL] | | -| Alt+KeyL | l | Alt+L | | Alt+L | alt+l | Alt+L | alt+[KeyL] | | -| Ctrl+Alt+KeyL | ¬ | Ctrl+Alt+L | | Ctrl+Alt+L | ctrl+alt+l | Ctrl+Alt+L | ctrl+alt+[KeyL] | | -| Shift+Alt+KeyL | L | Shift+Alt+L | | Shift+Alt+L | shift+alt+l | Shift+Alt+L | shift+alt+[KeyL] | | -| Ctrl+Shift+Alt+KeyL | ˆ | Ctrl+Shift+Alt+L | | Ctrl+Shift+Alt+L | ctrl+shift+alt+l | Ctrl+Shift+Alt+L | ctrl+shift+alt+[KeyL] | | +| Alt+KeyL | l | Alt+L | | Option+L | alt+l | Alt+L | alt+[KeyL] | | +| Ctrl+Alt+KeyL | ¬ | Ctrl+Alt+L | | Ctrl+Option+L | ctrl+alt+l | Ctrl+Alt+L | ctrl+alt+[KeyL] | | +| Shift+Alt+KeyL | L | Shift+Alt+L | | Shift+Option+L | shift+alt+l | Shift+Alt+L | shift+alt+[KeyL] | | +| Ctrl+Shift+Alt+KeyL | ˆ | Ctrl+Shift+Alt+L | | Ctrl+Shift+Option+L | ctrl+shift+alt+l | Ctrl+Shift+Alt+L | ctrl+shift+alt+[KeyL] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -120,38 +120,38 @@ isUSStandard: false | Ctrl+KeyM | m | Ctrl+M | | Ctrl+M | ctrl+m | Ctrl+M | ctrl+[KeyM] | | | Shift+KeyM | M | Shift+M | | Shift+M | shift+m | Shift+M | shift+[KeyM] | | | Ctrl+Shift+KeyM | M | Ctrl+Shift+M | | Ctrl+Shift+M | ctrl+shift+m | Ctrl+Shift+M | ctrl+shift+[KeyM] | | -| Alt+KeyM | m | Alt+M | | Alt+M | alt+m | Alt+M | alt+[KeyM] | | -| Ctrl+Alt+KeyM | µ | Ctrl+Alt+M | | Ctrl+Alt+M | ctrl+alt+m | Ctrl+Alt+M | ctrl+alt+[KeyM] | | -| Shift+Alt+KeyM | M | Shift+Alt+M | | Shift+Alt+M | shift+alt+m | Shift+Alt+M | shift+alt+[KeyM] | | -| Ctrl+Shift+Alt+KeyM | ˚ | Ctrl+Shift+Alt+M | | Ctrl+Shift+Alt+M | ctrl+shift+alt+m | Ctrl+Shift+Alt+M | ctrl+shift+alt+[KeyM] | | +| Alt+KeyM | m | Alt+M | | Option+M | alt+m | Alt+M | alt+[KeyM] | | +| Ctrl+Alt+KeyM | µ | Ctrl+Alt+M | | Ctrl+Option+M | ctrl+alt+m | Ctrl+Alt+M | ctrl+alt+[KeyM] | | +| Shift+Alt+KeyM | M | Shift+Alt+M | | Shift+Option+M | shift+alt+m | Shift+Alt+M | shift+alt+[KeyM] | | +| Ctrl+Shift+Alt+KeyM | ˚ | Ctrl+Shift+Alt+M | | Ctrl+Shift+Option+M | ctrl+shift+alt+m | Ctrl+Shift+Alt+M | ctrl+shift+alt+[KeyM] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyN | n | N | | N | n | N | [KeyN] | | | Ctrl+KeyN | n | Ctrl+N | | Ctrl+N | ctrl+n | Ctrl+N | ctrl+[KeyN] | | | Shift+KeyN | N | Shift+N | | Shift+N | shift+n | Shift+N | shift+[KeyN] | | | Ctrl+Shift+KeyN | N | Ctrl+Shift+N | | Ctrl+Shift+N | ctrl+shift+n | Ctrl+Shift+N | ctrl+shift+[KeyN] | | -| Alt+KeyN | n | Alt+N | | Alt+N | alt+n | Alt+N | alt+[KeyN] | | -| Ctrl+Alt+KeyN | ~ | Ctrl+Alt+N | | Ctrl+Alt+N | ctrl+alt+n | Ctrl+Alt+N | ctrl+alt+[KeyN] | | +| Alt+KeyN | n | Alt+N | | Option+N | alt+n | Alt+N | alt+[KeyN] | | +| Ctrl+Alt+KeyN | ~ | Ctrl+Alt+N | | Ctrl+Option+N | ctrl+alt+n | Ctrl+Alt+N | ctrl+alt+[KeyN] | | | | | Shift+` | | | | | | | -| Shift+Alt+KeyN | N | Shift+Alt+N | | Shift+Alt+N | shift+alt+n | Shift+Alt+N | shift+alt+[KeyN] | | -| Ctrl+Shift+Alt+KeyN | ˙ | Ctrl+Shift+Alt+N | | Ctrl+Shift+Alt+N | ctrl+shift+alt+n | Ctrl+Shift+Alt+N | ctrl+shift+alt+[KeyN] | | +| Shift+Alt+KeyN | N | Shift+Alt+N | | Shift+Option+N | shift+alt+n | Shift+Alt+N | shift+alt+[KeyN] | | +| Ctrl+Shift+Alt+KeyN | ˙ | Ctrl+Shift+Alt+N | | Ctrl+Shift+Option+N | ctrl+shift+alt+n | Ctrl+Shift+Alt+N | ctrl+shift+alt+[KeyN] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyO | o | O | | O | o | O | [KeyO] | | | Ctrl+KeyO | o | Ctrl+O | | Ctrl+O | ctrl+o | Ctrl+O | ctrl+[KeyO] | | | Shift+KeyO | O | Shift+O | | Shift+O | shift+o | Shift+O | shift+[KeyO] | | | Ctrl+Shift+KeyO | O | Ctrl+Shift+O | | Ctrl+Shift+O | ctrl+shift+o | Ctrl+Shift+O | ctrl+shift+[KeyO] | | -| Alt+KeyO | o | Alt+O | | Alt+O | alt+o | Alt+O | alt+[KeyO] | | -| Ctrl+Alt+KeyO | ø | Ctrl+Alt+O | | Ctrl+Alt+O | ctrl+alt+o | Ctrl+Alt+O | ctrl+alt+[KeyO] | | -| Shift+Alt+KeyO | O | Shift+Alt+O | | Shift+Alt+O | shift+alt+o | Shift+Alt+O | shift+alt+[KeyO] | | -| Ctrl+Shift+Alt+KeyO | Ø | Ctrl+Shift+Alt+O | | Ctrl+Shift+Alt+O | ctrl+shift+alt+o | Ctrl+Shift+Alt+O | ctrl+shift+alt+[KeyO] | | +| Alt+KeyO | o | Alt+O | | Option+O | alt+o | Alt+O | alt+[KeyO] | | +| Ctrl+Alt+KeyO | ø | Ctrl+Alt+O | | Ctrl+Option+O | ctrl+alt+o | Ctrl+Alt+O | ctrl+alt+[KeyO] | | +| Shift+Alt+KeyO | O | Shift+Alt+O | | Shift+Option+O | shift+alt+o | Shift+Alt+O | shift+alt+[KeyO] | | +| Ctrl+Shift+Alt+KeyO | Ø | Ctrl+Shift+Alt+O | | Ctrl+Shift+Option+O | ctrl+shift+alt+o | Ctrl+Shift+Alt+O | ctrl+shift+alt+[KeyO] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyP | p | P | | P | p | P | [KeyP] | | | Ctrl+KeyP | p | Ctrl+P | | Ctrl+P | ctrl+p | Ctrl+P | ctrl+[KeyP] | | | Shift+KeyP | P | Shift+P | | Shift+P | shift+p | Shift+P | shift+[KeyP] | | | Ctrl+Shift+KeyP | P | Ctrl+Shift+P | | Ctrl+Shift+P | ctrl+shift+p | Ctrl+Shift+P | ctrl+shift+[KeyP] | | -| Alt+KeyP | p | Alt+P | | Alt+P | alt+p | Alt+P | alt+[KeyP] | | -| Ctrl+Alt+KeyP | π | Ctrl+Alt+P | | Ctrl+Alt+P | ctrl+alt+p | Ctrl+Alt+P | ctrl+alt+[KeyP] | | -| Shift+Alt+KeyP | P | Shift+Alt+P | | Shift+Alt+P | shift+alt+p | Shift+Alt+P | shift+alt+[KeyP] | | -| Ctrl+Shift+Alt+KeyP | ∏ | Ctrl+Shift+Alt+P | | Ctrl+Shift+Alt+P | ctrl+shift+alt+p | Ctrl+Shift+Alt+P | ctrl+shift+alt+[KeyP] | | +| Alt+KeyP | p | Alt+P | | Option+P | alt+p | Alt+P | alt+[KeyP] | | +| Ctrl+Alt+KeyP | π | Ctrl+Alt+P | | Ctrl+Option+P | ctrl+alt+p | Ctrl+Alt+P | ctrl+alt+[KeyP] | | +| Shift+Alt+KeyP | P | Shift+Alt+P | | Shift+Option+P | shift+alt+p | Shift+Alt+P | shift+alt+[KeyP] | | +| Ctrl+Shift+Alt+KeyP | ∏ | Ctrl+Shift+Alt+P | | Ctrl+Shift+Option+P | ctrl+shift+alt+p | Ctrl+Shift+Alt+P | ctrl+shift+alt+[KeyP] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -159,37 +159,37 @@ isUSStandard: false | Ctrl+KeyQ | q | Ctrl+Q | | Ctrl+Q | ctrl+q | Ctrl+Q | ctrl+[KeyQ] | | | Shift+KeyQ | Q | Shift+Q | | Shift+Q | shift+q | Shift+Q | shift+[KeyQ] | | | Ctrl+Shift+KeyQ | Q | Ctrl+Shift+Q | | Ctrl+Shift+Q | ctrl+shift+q | Ctrl+Shift+Q | ctrl+shift+[KeyQ] | | -| Alt+KeyQ | q | Alt+Q | | Alt+Q | alt+q | Alt+Q | alt+[KeyQ] | | -| Ctrl+Alt+KeyQ | œ | Ctrl+Alt+Q | | Ctrl+Alt+Q | ctrl+alt+q | Ctrl+Alt+Q | ctrl+alt+[KeyQ] | | -| Shift+Alt+KeyQ | Q | Shift+Alt+Q | | Shift+Alt+Q | shift+alt+q | Shift+Alt+Q | shift+alt+[KeyQ] | | -| Ctrl+Shift+Alt+KeyQ | Œ | Ctrl+Shift+Alt+Q | | Ctrl+Shift+Alt+Q | ctrl+shift+alt+q | Ctrl+Shift+Alt+Q | ctrl+shift+alt+[KeyQ] | | +| Alt+KeyQ | q | Alt+Q | | Option+Q | alt+q | Alt+Q | alt+[KeyQ] | | +| Ctrl+Alt+KeyQ | œ | Ctrl+Alt+Q | | Ctrl+Option+Q | ctrl+alt+q | Ctrl+Alt+Q | ctrl+alt+[KeyQ] | | +| Shift+Alt+KeyQ | Q | Shift+Alt+Q | | Shift+Option+Q | shift+alt+q | Shift+Alt+Q | shift+alt+[KeyQ] | | +| Ctrl+Shift+Alt+KeyQ | Œ | Ctrl+Shift+Alt+Q | | Ctrl+Shift+Option+Q | ctrl+shift+alt+q | Ctrl+Shift+Alt+Q | ctrl+shift+alt+[KeyQ] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyR | r | R | | R | r | R | [KeyR] | | | Ctrl+KeyR | r | Ctrl+R | | Ctrl+R | ctrl+r | Ctrl+R | ctrl+[KeyR] | | | Shift+KeyR | R | Shift+R | | Shift+R | shift+r | Shift+R | shift+[KeyR] | | | Ctrl+Shift+KeyR | R | Ctrl+Shift+R | | Ctrl+Shift+R | ctrl+shift+r | Ctrl+Shift+R | ctrl+shift+[KeyR] | | -| Alt+KeyR | r | Alt+R | | Alt+R | alt+r | Alt+R | alt+[KeyR] | | -| Ctrl+Alt+KeyR | ® | Ctrl+Alt+R | | Ctrl+Alt+R | ctrl+alt+r | Ctrl+Alt+R | ctrl+alt+[KeyR] | | -| Shift+Alt+KeyR | R | Shift+Alt+R | | Shift+Alt+R | shift+alt+r | Shift+Alt+R | shift+alt+[KeyR] | | -| Ctrl+Shift+Alt+KeyR | È | Ctrl+Shift+Alt+R | | Ctrl+Shift+Alt+R | ctrl+shift+alt+r | Ctrl+Shift+Alt+R | ctrl+shift+alt+[KeyR] | | +| Alt+KeyR | r | Alt+R | | Option+R | alt+r | Alt+R | alt+[KeyR] | | +| Ctrl+Alt+KeyR | ® | Ctrl+Alt+R | | Ctrl+Option+R | ctrl+alt+r | Ctrl+Alt+R | ctrl+alt+[KeyR] | | +| Shift+Alt+KeyR | R | Shift+Alt+R | | Shift+Option+R | shift+alt+r | Shift+Alt+R | shift+alt+[KeyR] | | +| Ctrl+Shift+Alt+KeyR | È | Ctrl+Shift+Alt+R | | Ctrl+Shift+Option+R | ctrl+shift+alt+r | Ctrl+Shift+Alt+R | ctrl+shift+alt+[KeyR] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyS | s | S | | S | s | S | [KeyS] | | | Ctrl+KeyS | s | Ctrl+S | | Ctrl+S | ctrl+s | Ctrl+S | ctrl+[KeyS] | | | Shift+KeyS | S | Shift+S | | Shift+S | shift+s | Shift+S | shift+[KeyS] | | | Ctrl+Shift+KeyS | S | Ctrl+Shift+S | | Ctrl+Shift+S | ctrl+shift+s | Ctrl+Shift+S | ctrl+shift+[KeyS] | | -| Alt+KeyS | s | Alt+S | | Alt+S | alt+s | Alt+S | alt+[KeyS] | | -| Ctrl+Alt+KeyS | ß | Ctrl+Alt+S | | Ctrl+Alt+S | ctrl+alt+s | Ctrl+Alt+S | ctrl+alt+[KeyS] | | -| Shift+Alt+KeyS | S | Shift+Alt+S | | Shift+Alt+S | shift+alt+s | Shift+Alt+S | shift+alt+[KeyS] | | -| Ctrl+Shift+Alt+KeyS | fi | Ctrl+Shift+Alt+S | | Ctrl+Shift+Alt+S | ctrl+shift+alt+s | Ctrl+Shift+Alt+S | ctrl+shift+alt+[KeyS] | | +| Alt+KeyS | s | Alt+S | | Option+S | alt+s | Alt+S | alt+[KeyS] | | +| Ctrl+Alt+KeyS | ß | Ctrl+Alt+S | | Ctrl+Option+S | ctrl+alt+s | Ctrl+Alt+S | ctrl+alt+[KeyS] | | +| Shift+Alt+KeyS | S | Shift+Alt+S | | Shift+Option+S | shift+alt+s | Shift+Alt+S | shift+alt+[KeyS] | | +| Ctrl+Shift+Alt+KeyS | fi | Ctrl+Shift+Alt+S | | Ctrl+Shift+Option+S | ctrl+shift+alt+s | Ctrl+Shift+Alt+S | ctrl+shift+alt+[KeyS] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyT | t | T | | T | t | T | [KeyT] | | | Ctrl+KeyT | t | Ctrl+T | | Ctrl+T | ctrl+t | Ctrl+T | ctrl+[KeyT] | | | Shift+KeyT | T | Shift+T | | Shift+T | shift+t | Shift+T | shift+[KeyT] | | | Ctrl+Shift+KeyT | T | Ctrl+Shift+T | | Ctrl+Shift+T | ctrl+shift+t | Ctrl+Shift+T | ctrl+shift+[KeyT] | | -| Alt+KeyT | t | Alt+T | | Alt+T | alt+t | Alt+T | alt+[KeyT] | | -| Ctrl+Alt+KeyT | † | Ctrl+Alt+T | | Ctrl+Alt+T | ctrl+alt+t | Ctrl+Alt+T | ctrl+alt+[KeyT] | | -| Shift+Alt+KeyT | T | Shift+Alt+T | | Shift+Alt+T | shift+alt+t | Shift+Alt+T | shift+alt+[KeyT] | | -| Ctrl+Shift+Alt+KeyT | Î | Ctrl+Shift+Alt+T | | Ctrl+Shift+Alt+T | ctrl+shift+alt+t | Ctrl+Shift+Alt+T | ctrl+shift+alt+[KeyT] | | +| Alt+KeyT | t | Alt+T | | Option+T | alt+t | Alt+T | alt+[KeyT] | | +| Ctrl+Alt+KeyT | † | Ctrl+Alt+T | | Ctrl+Option+T | ctrl+alt+t | Ctrl+Alt+T | ctrl+alt+[KeyT] | | +| Shift+Alt+KeyT | T | Shift+Alt+T | | Shift+Option+T | shift+alt+t | Shift+Alt+T | shift+alt+[KeyT] | | +| Ctrl+Shift+Alt+KeyT | Î | Ctrl+Shift+Alt+T | | Ctrl+Shift+Option+T | ctrl+shift+alt+t | Ctrl+Shift+Alt+T | ctrl+shift+alt+[KeyT] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -197,37 +197,37 @@ isUSStandard: false | Ctrl+KeyU | u | Ctrl+U | | Ctrl+U | ctrl+u | Ctrl+U | ctrl+[KeyU] | | | Shift+KeyU | U | Shift+U | | Shift+U | shift+u | Shift+U | shift+[KeyU] | | | Ctrl+Shift+KeyU | U | Ctrl+Shift+U | | Ctrl+Shift+U | ctrl+shift+u | Ctrl+Shift+U | ctrl+shift+[KeyU] | | -| Alt+KeyU | u | Alt+U | | Alt+U | alt+u | Alt+U | alt+[KeyU] | | -| Ctrl+Alt+KeyU | ° | Ctrl+Alt+U | | Ctrl+Alt+U | ctrl+alt+u | Ctrl+Alt+U | ctrl+alt+[KeyU] | | -| Shift+Alt+KeyU | U | Shift+Alt+U | | Shift+Alt+U | shift+alt+u | Shift+Alt+U | shift+alt+[KeyU] | | -| Ctrl+Shift+Alt+KeyU | Ù | Ctrl+Shift+Alt+U | | Ctrl+Shift+Alt+U | ctrl+shift+alt+u | Ctrl+Shift+Alt+U | ctrl+shift+alt+[KeyU] | | +| Alt+KeyU | u | Alt+U | | Option+U | alt+u | Alt+U | alt+[KeyU] | | +| Ctrl+Alt+KeyU | ° | Ctrl+Alt+U | | Ctrl+Option+U | ctrl+alt+u | Ctrl+Alt+U | ctrl+alt+[KeyU] | | +| Shift+Alt+KeyU | U | Shift+Alt+U | | Shift+Option+U | shift+alt+u | Shift+Alt+U | shift+alt+[KeyU] | | +| Ctrl+Shift+Alt+KeyU | Ù | Ctrl+Shift+Alt+U | | Ctrl+Shift+Option+U | ctrl+shift+alt+u | Ctrl+Shift+Alt+U | ctrl+shift+alt+[KeyU] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyV | v | V | | V | v | V | [KeyV] | | | Ctrl+KeyV | v | Ctrl+V | | Ctrl+V | ctrl+v | Ctrl+V | ctrl+[KeyV] | | | Shift+KeyV | V | Shift+V | | Shift+V | shift+v | Shift+V | shift+[KeyV] | | | Ctrl+Shift+KeyV | V | Ctrl+Shift+V | | Ctrl+Shift+V | ctrl+shift+v | Ctrl+Shift+V | ctrl+shift+[KeyV] | | -| Alt+KeyV | v | Alt+V | | Alt+V | alt+v | Alt+V | alt+[KeyV] | | -| Ctrl+Alt+KeyV | √ | Ctrl+Alt+V | | Ctrl+Alt+V | ctrl+alt+v | Ctrl+Alt+V | ctrl+alt+[KeyV] | | -| Shift+Alt+KeyV | V | Shift+Alt+V | | Shift+Alt+V | shift+alt+v | Shift+Alt+V | shift+alt+[KeyV] | | -| Ctrl+Shift+Alt+KeyV | ◊ | Ctrl+Shift+Alt+V | | Ctrl+Shift+Alt+V | ctrl+shift+alt+v | Ctrl+Shift+Alt+V | ctrl+shift+alt+[KeyV] | | +| Alt+KeyV | v | Alt+V | | Option+V | alt+v | Alt+V | alt+[KeyV] | | +| Ctrl+Alt+KeyV | √ | Ctrl+Alt+V | | Ctrl+Option+V | ctrl+alt+v | Ctrl+Alt+V | ctrl+alt+[KeyV] | | +| Shift+Alt+KeyV | V | Shift+Alt+V | | Shift+Option+V | shift+alt+v | Shift+Alt+V | shift+alt+[KeyV] | | +| Ctrl+Shift+Alt+KeyV | ◊ | Ctrl+Shift+Alt+V | | Ctrl+Shift+Option+V | ctrl+shift+alt+v | Ctrl+Shift+Alt+V | ctrl+shift+alt+[KeyV] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyW | w | W | | W | w | W | [KeyW] | | | Ctrl+KeyW | w | Ctrl+W | | Ctrl+W | ctrl+w | Ctrl+W | ctrl+[KeyW] | | | Shift+KeyW | W | Shift+W | | Shift+W | shift+w | Shift+W | shift+[KeyW] | | | Ctrl+Shift+KeyW | W | Ctrl+Shift+W | | Ctrl+Shift+W | ctrl+shift+w | Ctrl+Shift+W | ctrl+shift+[KeyW] | | -| Alt+KeyW | w | Alt+W | | Alt+W | alt+w | Alt+W | alt+[KeyW] | | -| Ctrl+Alt+KeyW | ∑ | Ctrl+Alt+W | | Ctrl+Alt+W | ctrl+alt+w | Ctrl+Alt+W | ctrl+alt+[KeyW] | | -| Shift+Alt+KeyW | W | Shift+Alt+W | | Shift+Alt+W | shift+alt+w | Shift+Alt+W | shift+alt+[KeyW] | | -| Ctrl+Shift+Alt+KeyW | Á | Ctrl+Shift+Alt+W | | Ctrl+Shift+Alt+W | ctrl+shift+alt+w | Ctrl+Shift+Alt+W | ctrl+shift+alt+[KeyW] | | +| Alt+KeyW | w | Alt+W | | Option+W | alt+w | Alt+W | alt+[KeyW] | | +| Ctrl+Alt+KeyW | ∑ | Ctrl+Alt+W | | Ctrl+Option+W | ctrl+alt+w | Ctrl+Alt+W | ctrl+alt+[KeyW] | | +| Shift+Alt+KeyW | W | Shift+Alt+W | | Shift+Option+W | shift+alt+w | Shift+Alt+W | shift+alt+[KeyW] | | +| Ctrl+Shift+Alt+KeyW | Á | Ctrl+Shift+Alt+W | | Ctrl+Shift+Option+W | ctrl+shift+alt+w | Ctrl+Shift+Alt+W | ctrl+shift+alt+[KeyW] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyX | x | X | | X | x | X | [KeyX] | | | Ctrl+KeyX | x | Ctrl+X | | Ctrl+X | ctrl+x | Ctrl+X | ctrl+[KeyX] | | | Shift+KeyX | X | Shift+X | | Shift+X | shift+x | Shift+X | shift+[KeyX] | | | Ctrl+Shift+KeyX | X | Ctrl+Shift+X | | Ctrl+Shift+X | ctrl+shift+x | Ctrl+Shift+X | ctrl+shift+[KeyX] | | -| Alt+KeyX | x | Alt+X | | Alt+X | alt+x | Alt+X | alt+[KeyX] | | -| Ctrl+Alt+KeyX | ≈ | Ctrl+Alt+X | | Ctrl+Alt+X | ctrl+alt+x | Ctrl+Alt+X | ctrl+alt+[KeyX] | | -| Shift+Alt+KeyX | X | Shift+Alt+X | | Shift+Alt+X | shift+alt+x | Shift+Alt+X | shift+alt+[KeyX] | | -| Ctrl+Shift+Alt+KeyX | ™ | Ctrl+Shift+Alt+X | | Ctrl+Shift+Alt+X | ctrl+shift+alt+x | Ctrl+Shift+Alt+X | ctrl+shift+alt+[KeyX] | | +| Alt+KeyX | x | Alt+X | | Option+X | alt+x | Alt+X | alt+[KeyX] | | +| Ctrl+Alt+KeyX | ≈ | Ctrl+Alt+X | | Ctrl+Option+X | ctrl+alt+x | Ctrl+Alt+X | ctrl+alt+[KeyX] | | +| Shift+Alt+KeyX | X | Shift+Alt+X | | Shift+Option+X | shift+alt+x | Shift+Alt+X | shift+alt+[KeyX] | | +| Ctrl+Shift+Alt+KeyX | ™ | Ctrl+Shift+Alt+X | | Ctrl+Shift+Option+X | ctrl+shift+alt+x | Ctrl+Shift+Alt+X | ctrl+shift+alt+[KeyX] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -235,19 +235,19 @@ isUSStandard: false | Ctrl+KeyY | z | Ctrl+Z | | Ctrl+Z | ctrl+z | Ctrl+Z | ctrl+[KeyY] | | | Shift+KeyY | Z | Shift+Z | | Shift+Z | shift+z | Shift+Z | shift+[KeyY] | | | Ctrl+Shift+KeyY | Z | Ctrl+Shift+Z | | Ctrl+Shift+Z | ctrl+shift+z | Ctrl+Shift+Z | ctrl+shift+[KeyY] | | -| Alt+KeyY | z | Alt+Z | | Alt+Z | alt+z | Alt+Z | alt+[KeyY] | | -| Ctrl+Alt+KeyY | Ω | Ctrl+Alt+Z | | Ctrl+Alt+Z | ctrl+alt+z | Ctrl+Alt+Z | ctrl+alt+[KeyY] | | -| Shift+Alt+KeyY | Z | Shift+Alt+Z | | Shift+Alt+Z | shift+alt+z | Shift+Alt+Z | shift+alt+[KeyY] | | -| Ctrl+Shift+Alt+KeyY | Í | Ctrl+Shift+Alt+Z | | Ctrl+Shift+Alt+Z | ctrl+shift+alt+z | Ctrl+Shift+Alt+Z | ctrl+shift+alt+[KeyY] | | +| Alt+KeyY | z | Alt+Z | | Option+Z | alt+z | Alt+Z | alt+[KeyY] | | +| Ctrl+Alt+KeyY | Ω | Ctrl+Alt+Z | | Ctrl+Option+Z | ctrl+alt+z | Ctrl+Alt+Z | ctrl+alt+[KeyY] | | +| Shift+Alt+KeyY | Z | Shift+Alt+Z | | Shift+Option+Z | shift+alt+z | Shift+Alt+Z | shift+alt+[KeyY] | | +| Ctrl+Shift+Alt+KeyY | Í | Ctrl+Shift+Alt+Z | | Ctrl+Shift+Option+Z | ctrl+shift+alt+z | Ctrl+Shift+Alt+Z | ctrl+shift+alt+[KeyY] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyZ | y | Y | | Y | y | Y | [KeyZ] | | | Ctrl+KeyZ | y | Ctrl+Y | | Ctrl+Y | ctrl+y | Ctrl+Y | ctrl+[KeyZ] | | | Shift+KeyZ | Y | Shift+Y | | Shift+Y | shift+y | Shift+Y | shift+[KeyZ] | | | Ctrl+Shift+KeyZ | Y | Ctrl+Shift+Y | | Ctrl+Shift+Y | ctrl+shift+y | Ctrl+Shift+Y | ctrl+shift+[KeyZ] | | -| Alt+KeyZ | y | Alt+Y | | Alt+Y | alt+y | Alt+Y | alt+[KeyZ] | | -| Ctrl+Alt+KeyZ | ¥ | Ctrl+Alt+Y | | Ctrl+Alt+Y | ctrl+alt+y | Ctrl+Alt+Y | ctrl+alt+[KeyZ] | | -| Shift+Alt+KeyZ | Y | Shift+Alt+Y | | Shift+Alt+Y | shift+alt+y | Shift+Alt+Y | shift+alt+[KeyZ] | | -| Ctrl+Shift+Alt+KeyZ | Ÿ | Ctrl+Shift+Alt+Y | | Ctrl+Shift+Alt+Y | ctrl+shift+alt+y | Ctrl+Shift+Alt+Y | ctrl+shift+alt+[KeyZ] | | +| Alt+KeyZ | y | Alt+Y | | Option+Y | alt+y | Alt+Y | alt+[KeyZ] | | +| Ctrl+Alt+KeyZ | ¥ | Ctrl+Alt+Y | | Ctrl+Option+Y | ctrl+alt+y | Ctrl+Alt+Y | ctrl+alt+[KeyZ] | | +| Shift+Alt+KeyZ | Y | Shift+Alt+Y | | Shift+Option+Y | shift+alt+y | Shift+Alt+Y | shift+alt+[KeyZ] | | +| Ctrl+Shift+Alt+KeyZ | Ÿ | Ctrl+Shift+Alt+Y | | Ctrl+Shift+Option+Y | ctrl+shift+alt+y | Ctrl+Shift+Alt+Y | ctrl+shift+alt+[KeyZ] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit1 | 1 | 1 | | 1 | 1 | 1 | [Digit1] | | | Ctrl+Digit1 | 1 | Ctrl+1 | | Ctrl+1 | ctrl+1 | Ctrl+1 | ctrl+[Digit1] | | @@ -255,11 +255,11 @@ isUSStandard: false | | | Shift+= | | | | | | | | Ctrl+Shift+Digit1 | + | Ctrl+Shift+1 | | Ctrl+Shift+1 | ctrl+shift+1 | Ctrl+Shift+1 | ctrl+shift+[Digit1] | | | | | Ctrl+Shift+= | | | | | | | -| Alt+Digit1 | 1 | Alt+1 | | Alt+1 | alt+1 | Alt+1 | alt+[Digit1] | | -| Ctrl+Alt+Digit1 | ± | Ctrl+Alt+1 | | Ctrl+Alt+1 | ctrl+alt+1 | Ctrl+Alt+1 | ctrl+alt+[Digit1] | | -| Shift+Alt+Digit1 | + | Shift+Alt+1 | | Shift+Alt+1 | shift+alt+1 | Shift+Alt+1 | shift+alt+[Digit1] | | +| Alt+Digit1 | 1 | Alt+1 | | Option+1 | alt+1 | Alt+1 | alt+[Digit1] | | +| Ctrl+Alt+Digit1 | ± | Ctrl+Alt+1 | | Ctrl+Option+1 | ctrl+alt+1 | Ctrl+Alt+1 | ctrl+alt+[Digit1] | | +| Shift+Alt+Digit1 | + | Shift+Alt+1 | | Shift+Option+1 | shift+alt+1 | Shift+Alt+1 | shift+alt+[Digit1] | | | | | Shift+Alt+= | | | | | | | -| Ctrl+Shift+Alt+Digit1 | ∞ | Ctrl+Shift+Alt+1 | | Ctrl+Shift+Alt+1 | ctrl+shift+alt+1 | Ctrl+Shift+Alt+1 | ctrl+shift+alt+[Digit1] | | +| Ctrl+Shift+Alt+Digit1 | ∞ | Ctrl+Shift+Alt+1 | | Ctrl+Shift+Option+1 | ctrl+shift+alt+1 | Ctrl+Shift+Alt+1 | ctrl+shift+alt+[Digit1] | | | | | Ctrl+Shift+Alt+= | | | | | | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit2 | 2 | 2 | | 2 | 2 | 2 | [Digit2] | | @@ -268,11 +268,11 @@ isUSStandard: false | | | Shift+' | | | | | | | | Ctrl+Shift+Digit2 | " | Ctrl+Shift+2 | | Ctrl+Shift+2 | ctrl+shift+2 | Ctrl+Shift+2 | ctrl+shift+[Digit2] | | | | | Ctrl+Shift+' | | | | | | | -| Alt+Digit2 | 2 | Alt+2 | | Alt+2 | alt+2 | Alt+2 | alt+[Digit2] | | -| Ctrl+Alt+Digit2 | “ | Ctrl+Alt+2 | | Ctrl+Alt+2 | ctrl+alt+2 | Ctrl+Alt+2 | ctrl+alt+[Digit2] | | -| Shift+Alt+Digit2 | " | Shift+Alt+2 | | Shift+Alt+2 | shift+alt+2 | Shift+Alt+2 | shift+alt+[Digit2] | | +| Alt+Digit2 | 2 | Alt+2 | | Option+2 | alt+2 | Alt+2 | alt+[Digit2] | | +| Ctrl+Alt+Digit2 | “ | Ctrl+Alt+2 | | Ctrl+Option+2 | ctrl+alt+2 | Ctrl+Alt+2 | ctrl+alt+[Digit2] | | +| Shift+Alt+Digit2 | " | Shift+Alt+2 | | Shift+Option+2 | shift+alt+2 | Shift+Alt+2 | shift+alt+[Digit2] | | | | | Shift+Alt+' | | | | | | | -| Ctrl+Shift+Alt+Digit2 | ” | Ctrl+Shift+Alt+2 | | Ctrl+Shift+Alt+2 | ctrl+shift+alt+2 | Ctrl+Shift+Alt+2 | ctrl+shift+alt+[Digit2] | | +| Ctrl+Shift+Alt+Digit2 | ” | Ctrl+Shift+Alt+2 | | Ctrl+Shift+Option+2 | ctrl+shift+alt+2 | Ctrl+Shift+Alt+2 | ctrl+shift+alt+[Digit2] | | | | | Ctrl+Shift+Alt+' | | | | | | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | @@ -281,39 +281,39 @@ isUSStandard: false | Ctrl+Digit3 | 3 | Ctrl+3 | | Ctrl+3 | ctrl+3 | Ctrl+3 | ctrl+[Digit3] | | | Shift+Digit3 | * | Shift+3 | | Shift+3 | shift+3 | Shift+3 | shift+[Digit3] | | | Ctrl+Shift+Digit3 | * | Ctrl+Shift+3 | | Ctrl+Shift+3 | ctrl+shift+3 | Ctrl+Shift+3 | ctrl+shift+[Digit3] | | -| Alt+Digit3 | 3 | Alt+3 | | Alt+3 | alt+3 | Alt+3 | alt+[Digit3] | | -| Ctrl+Alt+Digit3 | # | Ctrl+Alt+3 | | Ctrl+Alt+3 | ctrl+alt+3 | Ctrl+Alt+3 | ctrl+alt+[Digit3] | | -| Shift+Alt+Digit3 | * | Shift+Alt+3 | | Shift+Alt+3 | shift+alt+3 | Shift+Alt+3 | shift+alt+[Digit3] | | -| Ctrl+Shift+Alt+Digit3 | ‹ | Ctrl+Shift+Alt+3 | | Ctrl+Shift+Alt+3 | ctrl+shift+alt+3 | Ctrl+Shift+Alt+3 | ctrl+shift+alt+[Digit3] | | +| Alt+Digit3 | 3 | Alt+3 | | Option+3 | alt+3 | Alt+3 | alt+[Digit3] | | +| Ctrl+Alt+Digit3 | # | Ctrl+Alt+3 | | Ctrl+Option+3 | ctrl+alt+3 | Ctrl+Alt+3 | ctrl+alt+[Digit3] | | +| Shift+Alt+Digit3 | * | Shift+Alt+3 | | Shift+Option+3 | shift+alt+3 | Shift+Alt+3 | shift+alt+[Digit3] | | +| Ctrl+Shift+Alt+Digit3 | ‹ | Ctrl+Shift+Alt+3 | | Ctrl+Shift+Option+3 | ctrl+shift+alt+3 | Ctrl+Shift+Alt+3 | ctrl+shift+alt+[Digit3] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit4 | 4 | 4 | | 4 | 4 | 4 | [Digit4] | | | Ctrl+Digit4 | 4 | Ctrl+4 | | Ctrl+4 | ctrl+4 | Ctrl+4 | ctrl+[Digit4] | | | Shift+Digit4 | ç | Shift+4 | | Shift+4 | shift+4 | Shift+4 | shift+[Digit4] | | | Ctrl+Shift+Digit4 | ç | Ctrl+Shift+4 | | Ctrl+Shift+4 | ctrl+shift+4 | Ctrl+Shift+4 | ctrl+shift+[Digit4] | | -| Alt+Digit4 | 4 | Alt+4 | | Alt+4 | alt+4 | Alt+4 | alt+[Digit4] | | -| Ctrl+Alt+Digit4 | Ç | Ctrl+Alt+4 | | Ctrl+Alt+4 | ctrl+alt+4 | Ctrl+Alt+4 | ctrl+alt+[Digit4] | | -| Shift+Alt+Digit4 | ç | Shift+Alt+4 | | Shift+Alt+4 | shift+alt+4 | Shift+Alt+4 | shift+alt+[Digit4] | | -| Ctrl+Shift+Alt+Digit4 | ⁄ | Ctrl+Shift+Alt+4 | | Ctrl+Shift+Alt+4 | ctrl+shift+alt+4 | Ctrl+Shift+Alt+4 | ctrl+shift+alt+[Digit4] | | +| Alt+Digit4 | 4 | Alt+4 | | Option+4 | alt+4 | Alt+4 | alt+[Digit4] | | +| Ctrl+Alt+Digit4 | Ç | Ctrl+Alt+4 | | Ctrl+Option+4 | ctrl+alt+4 | Ctrl+Alt+4 | ctrl+alt+[Digit4] | | +| Shift+Alt+Digit4 | ç | Shift+Alt+4 | | Shift+Option+4 | shift+alt+4 | Shift+Alt+4 | shift+alt+[Digit4] | | +| Ctrl+Shift+Alt+Digit4 | ⁄ | Ctrl+Shift+Alt+4 | | Ctrl+Shift+Option+4 | ctrl+shift+alt+4 | Ctrl+Shift+Alt+4 | ctrl+shift+alt+[Digit4] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit5 | 5 | 5 | | 5 | 5 | 5 | [Digit5] | | | Ctrl+Digit5 | 5 | Ctrl+5 | | Ctrl+5 | ctrl+5 | Ctrl+5 | ctrl+[Digit5] | | | Shift+Digit5 | % | Shift+5 | | Shift+5 | shift+5 | Shift+5 | shift+[Digit5] | | | Ctrl+Shift+Digit5 | % | Ctrl+Shift+5 | | Ctrl+Shift+5 | ctrl+shift+5 | Ctrl+Shift+5 | ctrl+shift+[Digit5] | | -| Alt+Digit5 | 5 | Alt+5 | | Alt+5 | alt+5 | Alt+5 | alt+[Digit5] | | -| Ctrl+Alt+Digit5 | [ | Ctrl+Alt+5 | | Ctrl+Alt+5 | ctrl+alt+5 | Ctrl+Alt+5 | ctrl+alt+[Digit5] | | +| Alt+Digit5 | 5 | Alt+5 | | Option+5 | alt+5 | Alt+5 | alt+[Digit5] | | +| Ctrl+Alt+Digit5 | [ | Ctrl+Alt+5 | | Ctrl+Option+5 | ctrl+alt+5 | Ctrl+Alt+5 | ctrl+alt+[Digit5] | | | | | [ | | | | | | | -| Shift+Alt+Digit5 | % | Shift+Alt+5 | | Shift+Alt+5 | shift+alt+5 | Shift+Alt+5 | shift+alt+[Digit5] | | -| Ctrl+Shift+Alt+Digit5 | [ | Ctrl+Shift+Alt+5 | | Ctrl+Shift+Alt+5 | ctrl+shift+alt+5 | Ctrl+Shift+Alt+5 | ctrl+shift+alt+[Digit5] | | +| Shift+Alt+Digit5 | % | Shift+Alt+5 | | Shift+Option+5 | shift+alt+5 | Shift+Alt+5 | shift+alt+[Digit5] | | +| Ctrl+Shift+Alt+Digit5 | [ | Ctrl+Shift+Alt+5 | | Ctrl+Shift+Option+5 | ctrl+shift+alt+5 | Ctrl+Shift+Alt+5 | ctrl+shift+alt+[Digit5] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit6 | 6 | 6 | | 6 | 6 | 6 | [Digit6] | | | Ctrl+Digit6 | 6 | Ctrl+6 | | Ctrl+6 | ctrl+6 | Ctrl+6 | ctrl+[Digit6] | | | Shift+Digit6 | & | Shift+6 | | Shift+6 | shift+6 | Shift+6 | shift+[Digit6] | | | Ctrl+Shift+Digit6 | & | Ctrl+Shift+6 | | Ctrl+Shift+6 | ctrl+shift+6 | Ctrl+Shift+6 | ctrl+shift+[Digit6] | | -| Alt+Digit6 | 6 | Alt+6 | | Alt+6 | alt+6 | Alt+6 | alt+[Digit6] | | -| Ctrl+Alt+Digit6 | ] | Ctrl+Alt+6 | | Ctrl+Alt+6 | ctrl+alt+6 | Ctrl+Alt+6 | ctrl+alt+[Digit6] | | +| Alt+Digit6 | 6 | Alt+6 | | Option+6 | alt+6 | Alt+6 | alt+[Digit6] | | +| Ctrl+Alt+Digit6 | ] | Ctrl+Alt+6 | | Ctrl+Option+6 | ctrl+alt+6 | Ctrl+Alt+6 | ctrl+alt+[Digit6] | | | | | ] | | | | | | | -| Shift+Alt+Digit6 | & | Shift+Alt+6 | | Shift+Alt+6 | shift+alt+6 | Shift+Alt+6 | shift+alt+[Digit6] | | -| Ctrl+Shift+Alt+Digit6 | ] | Ctrl+Shift+Alt+6 | | Ctrl+Shift+Alt+6 | ctrl+shift+alt+6 | Ctrl+Shift+Alt+6 | ctrl+shift+alt+[Digit6] | | +| Shift+Alt+Digit6 | & | Shift+Alt+6 | | Shift+Option+6 | shift+alt+6 | Shift+Alt+6 | shift+alt+[Digit6] | | +| Ctrl+Shift+Alt+Digit6 | ] | Ctrl+Shift+Alt+6 | | Ctrl+Shift+Option+6 | ctrl+shift+alt+6 | Ctrl+Shift+Alt+6 | ctrl+shift+alt+[Digit6] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -323,33 +323,33 @@ isUSStandard: false | | | / | | | | | | | | Ctrl+Shift+Digit7 | / | Ctrl+Shift+7 | | Ctrl+Shift+7 | ctrl+shift+7 | Ctrl+Shift+7 | ctrl+shift+[Digit7] | | | | | Ctrl+/ | | | | | | | -| Alt+Digit7 | 7 | Alt+7 | | Alt+7 | alt+7 | Alt+7 | alt+[Digit7] | | -| Ctrl+Alt+Digit7 | | | Ctrl+Alt+7 | | Ctrl+Alt+7 | ctrl+alt+7 | Ctrl+Alt+7 | ctrl+alt+[Digit7] | | +| Alt+Digit7 | 7 | Alt+7 | | Option+7 | alt+7 | Alt+7 | alt+[Digit7] | | +| Ctrl+Alt+Digit7 | | | Ctrl+Alt+7 | | Ctrl+Option+7 | ctrl+alt+7 | Ctrl+Alt+7 | ctrl+alt+[Digit7] | | | | | Shift+\ | | | | | | | -| Shift+Alt+Digit7 | / | Shift+Alt+7 | | Shift+Alt+7 | shift+alt+7 | Shift+Alt+7 | shift+alt+[Digit7] | | +| Shift+Alt+Digit7 | / | Shift+Alt+7 | | Shift+Option+7 | shift+alt+7 | Shift+Alt+7 | shift+alt+[Digit7] | | | | | Alt+/ | | | | | | | -| Ctrl+Shift+Alt+Digit7 | \ | Ctrl+Shift+Alt+7 | | Ctrl+Shift+Alt+7 | ctrl+shift+alt+7 | Ctrl+Shift+Alt+7 | ctrl+shift+alt+[Digit7] | | +| Ctrl+Shift+Alt+Digit7 | \ | Ctrl+Shift+Alt+7 | | Ctrl+Shift+Option+7 | ctrl+shift+alt+7 | Ctrl+Shift+Alt+7 | ctrl+shift+alt+[Digit7] | | | | | \ | | | | | | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit8 | 8 | 8 | | 8 | 8 | 8 | [Digit8] | | | Ctrl+Digit8 | 8 | Ctrl+8 | | Ctrl+8 | ctrl+8 | Ctrl+8 | ctrl+[Digit8] | | | Shift+Digit8 | ( | Shift+8 | | Shift+8 | shift+8 | Shift+8 | shift+[Digit8] | | | Ctrl+Shift+Digit8 | ( | Ctrl+Shift+8 | | Ctrl+Shift+8 | ctrl+shift+8 | Ctrl+Shift+8 | ctrl+shift+[Digit8] | | -| Alt+Digit8 | 8 | Alt+8 | | Alt+8 | alt+8 | Alt+8 | alt+[Digit8] | | -| Ctrl+Alt+Digit8 | { | Ctrl+Alt+8 | | Ctrl+Alt+8 | ctrl+alt+8 | Ctrl+Alt+8 | ctrl+alt+[Digit8] | | +| Alt+Digit8 | 8 | Alt+8 | | Option+8 | alt+8 | Alt+8 | alt+[Digit8] | | +| Ctrl+Alt+Digit8 | { | Ctrl+Alt+8 | | Ctrl+Option+8 | ctrl+alt+8 | Ctrl+Alt+8 | ctrl+alt+[Digit8] | | | | | Shift+[ | | | | | | | -| Shift+Alt+Digit8 | ( | Shift+Alt+8 | | Shift+Alt+8 | shift+alt+8 | Shift+Alt+8 | shift+alt+[Digit8] | | -| Ctrl+Shift+Alt+Digit8 | Ò | Ctrl+Shift+Alt+8 | | Ctrl+Shift+Alt+8 | ctrl+shift+alt+8 | Ctrl+Shift+Alt+8 | ctrl+shift+alt+[Digit8] | | +| Shift+Alt+Digit8 | ( | Shift+Alt+8 | | Shift+Option+8 | shift+alt+8 | Shift+Alt+8 | shift+alt+[Digit8] | | +| Ctrl+Shift+Alt+Digit8 | Ò | Ctrl+Shift+Alt+8 | | Ctrl+Shift+Option+8 | ctrl+shift+alt+8 | Ctrl+Shift+Alt+8 | ctrl+shift+alt+[Digit8] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit9 | 9 | 9 | | 9 | 9 | 9 | [Digit9] | | | Ctrl+Digit9 | 9 | Ctrl+9 | | Ctrl+9 | ctrl+9 | Ctrl+9 | ctrl+[Digit9] | | | Shift+Digit9 | ) | Shift+9 | | Shift+9 | shift+9 | Shift+9 | shift+[Digit9] | | | Ctrl+Shift+Digit9 | ) | Ctrl+Shift+9 | | Ctrl+Shift+9 | ctrl+shift+9 | Ctrl+Shift+9 | ctrl+shift+[Digit9] | | -| Alt+Digit9 | 9 | Alt+9 | | Alt+9 | alt+9 | Alt+9 | alt+[Digit9] | | -| Ctrl+Alt+Digit9 | } | Ctrl+Alt+9 | | Ctrl+Alt+9 | ctrl+alt+9 | Ctrl+Alt+9 | ctrl+alt+[Digit9] | | +| Alt+Digit9 | 9 | Alt+9 | | Option+9 | alt+9 | Alt+9 | alt+[Digit9] | | +| Ctrl+Alt+Digit9 | } | Ctrl+Alt+9 | | Ctrl+Option+9 | ctrl+alt+9 | Ctrl+Alt+9 | ctrl+alt+[Digit9] | | | | | Shift+] | | | | | | | -| Shift+Alt+Digit9 | ) | Shift+Alt+9 | | Shift+Alt+9 | shift+alt+9 | Shift+Alt+9 | shift+alt+[Digit9] | | -| Ctrl+Shift+Alt+Digit9 | Ô | Ctrl+Shift+Alt+9 | | Ctrl+Shift+Alt+9 | ctrl+shift+alt+9 | Ctrl+Shift+Alt+9 | ctrl+shift+alt+[Digit9] | | +| Shift+Alt+Digit9 | ) | Shift+Alt+9 | | Shift+Option+9 | shift+alt+9 | Shift+Alt+9 | shift+alt+[Digit9] | | +| Ctrl+Shift+Alt+Digit9 | Ô | Ctrl+Shift+Alt+9 | | Ctrl+Shift+Option+9 | ctrl+shift+alt+9 | Ctrl+Shift+Alt+9 | ctrl+shift+alt+[Digit9] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit0 | 0 | 0 | | 0 | 0 | 0 | [Digit0] | | | Ctrl+Digit0 | 0 | Ctrl+0 | | Ctrl+0 | ctrl+0 | Ctrl+0 | ctrl+[Digit0] | | @@ -357,11 +357,11 @@ isUSStandard: false | | | = | | | | | | | | Ctrl+Shift+Digit0 | = | Ctrl+Shift+0 | | Ctrl+Shift+0 | ctrl+shift+0 | Ctrl+Shift+0 | ctrl+shift+[Digit0] | | | | | Ctrl+= | | | | | | | -| Alt+Digit0 | 0 | Alt+0 | | Alt+0 | alt+0 | Alt+0 | alt+[Digit0] | | -| Ctrl+Alt+Digit0 | ≠ | Ctrl+Alt+0 | | Ctrl+Alt+0 | ctrl+alt+0 | Ctrl+Alt+0 | ctrl+alt+[Digit0] | | -| Shift+Alt+Digit0 | = | Shift+Alt+0 | | Shift+Alt+0 | shift+alt+0 | Shift+Alt+0 | shift+alt+[Digit0] | | +| Alt+Digit0 | 0 | Alt+0 | | Option+0 | alt+0 | Alt+0 | alt+[Digit0] | | +| Ctrl+Alt+Digit0 | ≠ | Ctrl+Alt+0 | | Ctrl+Option+0 | ctrl+alt+0 | Ctrl+Alt+0 | ctrl+alt+[Digit0] | | +| Shift+Alt+Digit0 | = | Shift+Alt+0 | | Shift+Option+0 | shift+alt+0 | Shift+Alt+0 | shift+alt+[Digit0] | | | | | Alt+= | | | | | | | -| Ctrl+Shift+Alt+Digit0 | Ú | Ctrl+Shift+Alt+0 | | Ctrl+Shift+Alt+0 | ctrl+shift+alt+0 | Ctrl+Shift+Alt+0 | ctrl+shift+alt+[Digit0] | | +| Ctrl+Shift+Alt+Digit0 | Ú | Ctrl+Shift+Alt+0 | | Ctrl+Shift+Option+0 | ctrl+shift+alt+0 | Ctrl+Shift+Alt+0 | ctrl+shift+alt+[Digit0] | | | | | Ctrl+Alt+= | | | | | | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | @@ -370,37 +370,37 @@ isUSStandard: false | Ctrl+Minus | ' | Ctrl+' | | Ctrl+' | ctrl+[Minus] | null | ctrl+[Minus] | NO | | Shift+Minus | ? | Shift+/ | | Shift+' | shift+[Minus] | null | shift+[Minus] | NO | | Ctrl+Shift+Minus | ? | Ctrl+Shift+/ | | Ctrl+Shift+' | ctrl+shift+[Minus] | null | ctrl+shift+[Minus] | NO | -| Alt+Minus | ' | Alt+' | | Alt+' | alt+[Minus] | null | alt+[Minus] | NO | -| Ctrl+Alt+Minus | ¿ | Ctrl+Alt+' | | Ctrl+Alt+' | ctrl+alt+[Minus] | null | ctrl+alt+[Minus] | NO | -| Shift+Alt+Minus | ? | Shift+Alt+/ | | Shift+Alt+' | shift+alt+[Minus] | null | shift+alt+[Minus] | NO | -| Ctrl+Shift+Alt+Minus |  | Ctrl+Shift+Alt+/ | | Ctrl+Shift+Alt+' | ctrl+shift+alt+[Minus] | null | ctrl+shift+alt+[Minus] | NO | +| Alt+Minus | ' | Alt+' | | Option+' | alt+[Minus] | null | alt+[Minus] | NO | +| Ctrl+Alt+Minus | ¿ | Ctrl+Alt+' | | Ctrl+Option+' | ctrl+alt+[Minus] | null | ctrl+alt+[Minus] | NO | +| Shift+Alt+Minus | ? | Shift+Alt+/ | | Shift+Option+' | shift+alt+[Minus] | null | shift+alt+[Minus] | NO | +| Ctrl+Shift+Alt+Minus |  | Ctrl+Shift+Alt+/ | | Ctrl+Shift+Option+' | ctrl+shift+alt+[Minus] | null | ctrl+shift+alt+[Minus] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Equal | ^ | | | ^ | [Equal] | null | [Equal] | NO | | Ctrl+Equal | ^ | | | Ctrl+^ | ctrl+[Equal] | null | ctrl+[Equal] | NO | | Shift+Equal | ` | ` | | Shift+^ | shift+[Equal] | null | shift+[Equal] | NO | | Ctrl+Shift+Equal | ` | Ctrl+` | | Ctrl+Shift+^ | ctrl+shift+[Equal] | null | ctrl+shift+[Equal] | NO | -| Alt+Equal | ^ | | | Alt+^ | alt+[Equal] | null | alt+[Equal] | NO | -| Ctrl+Alt+Equal | ´ | | | Ctrl+Alt+^ | ctrl+alt+[Equal] | null | ctrl+alt+[Equal] | NO | -| Shift+Alt+Equal | ` | Alt+` | | Shift+Alt+^ | shift+alt+[Equal] | null | shift+alt+[Equal] | NO | -| Ctrl+Shift+Alt+Equal | ^ | Ctrl+Alt+` | | Ctrl+Shift+Alt+^ | ctrl+shift+alt+[Equal] | null | ctrl+shift+alt+[Equal] | NO | +| Alt+Equal | ^ | | | Option+^ | alt+[Equal] | null | alt+[Equal] | NO | +| Ctrl+Alt+Equal | ´ | | | Ctrl+Option+^ | ctrl+alt+[Equal] | null | ctrl+alt+[Equal] | NO | +| Shift+Alt+Equal | ` | Alt+` | | Shift+Option+^ | shift+alt+[Equal] | null | shift+alt+[Equal] | NO | +| Ctrl+Shift+Alt+Equal | ^ | Ctrl+Alt+` | | Ctrl+Shift+Option+^ | ctrl+shift+alt+[Equal] | null | ctrl+shift+alt+[Equal] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | BracketLeft | ü | | | ü | [BracketLeft] | null | [BracketLeft] | NO | | Ctrl+BracketLeft | ü | | | Ctrl+ü | ctrl+[BracketLeft] | null | ctrl+[BracketLeft] | NO | | Shift+BracketLeft | è | | | Shift+ü | shift+[BracketLeft] | null | shift+[BracketLeft] | NO | | Ctrl+Shift+BracketLeft | è | | | Ctrl+Shift+ü | ctrl+shift+[BracketLeft] | null | ctrl+shift+[BracketLeft] | NO | -| Alt+BracketLeft | ü | | | Alt+ü | alt+[BracketLeft] | null | alt+[BracketLeft] | NO | -| Ctrl+Alt+BracketLeft | § | | | Ctrl+Alt+ü | ctrl+alt+[BracketLeft] | null | ctrl+alt+[BracketLeft] | NO | -| Shift+Alt+BracketLeft | è | | | Shift+Alt+ü | shift+alt+[BracketLeft] | null | shift+alt+[BracketLeft] | NO | -| Ctrl+Shift+Alt+BracketLeft | ÿ | | | Ctrl+Shift+Alt+ü | ctrl+shift+alt+[BracketLeft] | null | ctrl+shift+alt+[BracketLeft] | NO | +| Alt+BracketLeft | ü | | | Option+ü | alt+[BracketLeft] | null | alt+[BracketLeft] | NO | +| Ctrl+Alt+BracketLeft | § | | | Ctrl+Option+ü | ctrl+alt+[BracketLeft] | null | ctrl+alt+[BracketLeft] | NO | +| Shift+Alt+BracketLeft | è | | | Shift+Option+ü | shift+alt+[BracketLeft] | null | shift+alt+[BracketLeft] | NO | +| Ctrl+Shift+Alt+BracketLeft | ÿ | | | Ctrl+Shift+Option+ü | ctrl+shift+alt+[BracketLeft] | null | ctrl+shift+alt+[BracketLeft] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | BracketRight | ¨ | | | ¨ | [BracketRight] | null | [BracketRight] | NO | | Ctrl+BracketRight | ¨ | | | Ctrl+¨ | ctrl+[BracketRight] | null | ctrl+[BracketRight] | NO | | Shift+BracketRight | ! | | | Shift+¨ | shift+[BracketRight] | null | shift+[BracketRight] | NO | | Ctrl+Shift+BracketRight | ! | | | Ctrl+Shift+¨ | ctrl+shift+[BracketRight] | null | ctrl+shift+[BracketRight] | NO | -| Alt+BracketRight | ¨ | | | Alt+¨ | alt+[BracketRight] | null | alt+[BracketRight] | NO | -| Ctrl+Alt+BracketRight | ‘ | | | Ctrl+Alt+¨ | ctrl+alt+[BracketRight] | null | ctrl+alt+[BracketRight] | NO | -| Shift+Alt+BracketRight | ! | | | Shift+Alt+¨ | shift+alt+[BracketRight] | null | shift+alt+[BracketRight] | NO | -| Ctrl+Shift+Alt+BracketRight | ’ | | | Ctrl+Shift+Alt+¨ | ctrl+shift+alt+[BracketRight] | null | ctrl+shift+alt+[BracketRight] | NO | +| Alt+BracketRight | ¨ | | | Option+¨ | alt+[BracketRight] | null | alt+[BracketRight] | NO | +| Ctrl+Alt+BracketRight | ‘ | | | Ctrl+Option+¨ | ctrl+alt+[BracketRight] | null | ctrl+alt+[BracketRight] | NO | +| Shift+Alt+BracketRight | ! | | | Shift+Option+¨ | shift+alt+[BracketRight] | null | shift+alt+[BracketRight] | NO | +| Ctrl+Shift+Alt+BracketRight | ’ | | | Ctrl+Shift+Option+¨ | ctrl+shift+alt+[BracketRight] | null | ctrl+shift+alt+[BracketRight] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -408,10 +408,10 @@ isUSStandard: false | Ctrl+Backslash | $ | | | Ctrl+$ | ctrl+[Backslash] | null | ctrl+[Backslash] | NO | | Shift+Backslash | £ | | | Shift+$ | shift+[Backslash] | null | shift+[Backslash] | NO | | Ctrl+Shift+Backslash | £ | | | Ctrl+Shift+$ | ctrl+shift+[Backslash] | null | ctrl+shift+[Backslash] | NO | -| Alt+Backslash | $ | | | Alt+$ | alt+[Backslash] | null | alt+[Backslash] | NO | -| Ctrl+Alt+Backslash | ¶ | | | Ctrl+Alt+$ | ctrl+alt+[Backslash] | null | ctrl+alt+[Backslash] | NO | -| Shift+Alt+Backslash | £ | | | Shift+Alt+$ | shift+alt+[Backslash] | null | shift+alt+[Backslash] | NO | -| Ctrl+Shift+Alt+Backslash | • | | | Ctrl+Shift+Alt+$ | ctrl+shift+alt+[Backslash] | null | ctrl+shift+alt+[Backslash] | NO | +| Alt+Backslash | $ | | | Option+$ | alt+[Backslash] | null | alt+[Backslash] | NO | +| Ctrl+Alt+Backslash | ¶ | | | Ctrl+Option+$ | ctrl+alt+[Backslash] | null | ctrl+alt+[Backslash] | NO | +| Shift+Alt+Backslash | £ | | | Shift+Option+$ | shift+alt+[Backslash] | null | shift+alt+[Backslash] | NO | +| Ctrl+Shift+Alt+Backslash | • | | | Ctrl+Shift+Option+$ | ctrl+shift+alt+[Backslash] | null | ctrl+shift+alt+[Backslash] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | IntlHash | --- | | | null | null | null | null | | | Ctrl+IntlHash | --- | | | null | null | null | null | | @@ -426,19 +426,19 @@ isUSStandard: false | Ctrl+Semicolon | ö | | | Ctrl+ö | ctrl+[Semicolon] | null | ctrl+[Semicolon] | NO | | Shift+Semicolon | é | | | Shift+ö | shift+[Semicolon] | null | shift+[Semicolon] | NO | | Ctrl+Shift+Semicolon | é | | | Ctrl+Shift+ö | ctrl+shift+[Semicolon] | null | ctrl+shift+[Semicolon] | NO | -| Alt+Semicolon | ö | | | Alt+ö | alt+[Semicolon] | null | alt+[Semicolon] | NO | -| Ctrl+Alt+Semicolon | ¢ | | | Ctrl+Alt+ö | ctrl+alt+[Semicolon] | null | ctrl+alt+[Semicolon] | NO | -| Shift+Alt+Semicolon | é | | | Shift+Alt+ö | shift+alt+[Semicolon] | null | shift+alt+[Semicolon] | NO | -| Ctrl+Shift+Alt+Semicolon | ˘ | | | Ctrl+Shift+Alt+ö | ctrl+shift+alt+[Semicolon] | null | ctrl+shift+alt+[Semicolon] | NO | +| Alt+Semicolon | ö | | | Option+ö | alt+[Semicolon] | null | alt+[Semicolon] | NO | +| Ctrl+Alt+Semicolon | ¢ | | | Ctrl+Option+ö | ctrl+alt+[Semicolon] | null | ctrl+alt+[Semicolon] | NO | +| Shift+Alt+Semicolon | é | | | Shift+Option+ö | shift+alt+[Semicolon] | null | shift+alt+[Semicolon] | NO | +| Ctrl+Shift+Alt+Semicolon | ˘ | | | Ctrl+Shift+Option+ö | ctrl+shift+alt+[Semicolon] | null | ctrl+shift+alt+[Semicolon] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Quote | ä | | | ä | [Quote] | null | [Quote] | NO | | Ctrl+Quote | ä | | | Ctrl+ä | ctrl+[Quote] | null | ctrl+[Quote] | NO | | Shift+Quote | à | | | Shift+ä | shift+[Quote] | null | shift+[Quote] | NO | | Ctrl+Shift+Quote | à | | | Ctrl+Shift+ä | ctrl+shift+[Quote] | null | ctrl+shift+[Quote] | NO | -| Alt+Quote | ä | | | Alt+ä | alt+[Quote] | null | alt+[Quote] | NO | -| Ctrl+Alt+Quote | æ | | | Ctrl+Alt+ä | ctrl+alt+[Quote] | null | ctrl+alt+[Quote] | NO | -| Shift+Alt+Quote | à | | | Shift+Alt+ä | shift+alt+[Quote] | null | shift+alt+[Quote] | NO | -| Ctrl+Shift+Alt+Quote | Æ | | | Ctrl+Shift+Alt+ä | ctrl+shift+alt+[Quote] | null | ctrl+shift+alt+[Quote] | NO | +| Alt+Quote | ä | | | Option+ä | alt+[Quote] | null | alt+[Quote] | NO | +| Ctrl+Alt+Quote | æ | | | Ctrl+Option+ä | ctrl+alt+[Quote] | null | ctrl+alt+[Quote] | NO | +| Shift+Alt+Quote | à | | | Shift+Option+ä | shift+alt+[Quote] | null | shift+alt+[Quote] | NO | +| Ctrl+Shift+Alt+Quote | Æ | | | Ctrl+Shift+Option+ä | ctrl+shift+alt+[Quote] | null | ctrl+shift+alt+[Quote] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -446,37 +446,37 @@ isUSStandard: false | Ctrl+Backquote | < | Ctrl+Shift+, | | Ctrl+< | ctrl+[Backquote] | null | ctrl+[Backquote] | NO | | Shift+Backquote | > | Shift+. | | Shift+< | shift+[Backquote] | null | shift+[Backquote] | NO | | Ctrl+Shift+Backquote | > | Ctrl+Shift+. | | Ctrl+Shift+< | ctrl+shift+[Backquote] | null | ctrl+shift+[Backquote] | NO | -| Alt+Backquote | < | Shift+Alt+, | | Alt+< | alt+[Backquote] | null | alt+[Backquote] | NO | -| Ctrl+Alt+Backquote | ≤ | Ctrl+Shift+Alt+, | | Ctrl+Alt+< | ctrl+alt+[Backquote] | null | ctrl+alt+[Backquote] | NO | -| Shift+Alt+Backquote | > | Shift+Alt+. | | Shift+Alt+< | shift+alt+[Backquote] | null | shift+alt+[Backquote] | NO | -| Ctrl+Shift+Alt+Backquote | ≥ | Ctrl+Shift+Alt+. | | Ctrl+Shift+Alt+< | ctrl+shift+alt+[Backquote] | null | ctrl+shift+alt+[Backquote] | NO | +| Alt+Backquote | < | Shift+Alt+, | | Option+< | alt+[Backquote] | null | alt+[Backquote] | NO | +| Ctrl+Alt+Backquote | ≤ | Ctrl+Shift+Alt+, | | Ctrl+Option+< | ctrl+alt+[Backquote] | null | ctrl+alt+[Backquote] | NO | +| Shift+Alt+Backquote | > | Shift+Alt+. | | Shift+Option+< | shift+alt+[Backquote] | null | shift+alt+[Backquote] | NO | +| Ctrl+Shift+Alt+Backquote | ≥ | Ctrl+Shift+Alt+. | | Ctrl+Shift+Option+< | ctrl+shift+alt+[Backquote] | null | ctrl+shift+alt+[Backquote] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Comma | , | , | | , | [Comma] | null | [Comma] | NO | | Ctrl+Comma | , | Ctrl+, | | Ctrl+, | ctrl+[Comma] | null | ctrl+[Comma] | NO | | Shift+Comma | ; | ; | | Shift+, | shift+[Comma] | null | shift+[Comma] | NO | | Ctrl+Shift+Comma | ; | Ctrl+; | | Ctrl+Shift+, | ctrl+shift+[Comma] | null | ctrl+shift+[Comma] | NO | -| Alt+Comma | , | Alt+, | | Alt+, | alt+[Comma] | null | alt+[Comma] | NO | -| Ctrl+Alt+Comma | « | Ctrl+Alt+, | | Ctrl+Alt+, | ctrl+alt+[Comma] | null | ctrl+alt+[Comma] | NO | -| Shift+Alt+Comma | ; | Alt+; | | Shift+Alt+, | shift+alt+[Comma] | null | shift+alt+[Comma] | NO | -| Ctrl+Shift+Alt+Comma | » | Ctrl+Alt+; | | Ctrl+Shift+Alt+, | ctrl+shift+alt+[Comma] | null | ctrl+shift+alt+[Comma] | NO | +| Alt+Comma | , | Alt+, | | Option+, | alt+[Comma] | null | alt+[Comma] | NO | +| Ctrl+Alt+Comma | « | Ctrl+Alt+, | | Ctrl+Option+, | ctrl+alt+[Comma] | null | ctrl+alt+[Comma] | NO | +| Shift+Alt+Comma | ; | Alt+; | | Shift+Option+, | shift+alt+[Comma] | null | shift+alt+[Comma] | NO | +| Ctrl+Shift+Alt+Comma | » | Ctrl+Alt+; | | Ctrl+Shift+Option+, | ctrl+shift+alt+[Comma] | null | ctrl+shift+alt+[Comma] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Period | . | . | | . | [Period] | null | [Period] | NO | | Ctrl+Period | . | Ctrl+. | | Ctrl+. | ctrl+[Period] | null | ctrl+[Period] | NO | | Shift+Period | : | Shift+; | | Shift+. | shift+[Period] | null | shift+[Period] | NO | | Ctrl+Shift+Period | : | Ctrl+Shift+; | | Ctrl+Shift+. | ctrl+shift+[Period] | null | ctrl+shift+[Period] | NO | -| Alt+Period | . | Alt+. | | Alt+. | alt+[Period] | null | alt+[Period] | NO | -| Ctrl+Alt+Period | … | Ctrl+Alt+. | | Ctrl+Alt+. | ctrl+alt+[Period] | null | ctrl+alt+[Period] | NO | -| Shift+Alt+Period | : | Shift+Alt+; | | Shift+Alt+. | shift+alt+[Period] | null | shift+alt+[Period] | NO | -| Ctrl+Shift+Alt+Period | ÷ | Ctrl+Shift+Alt+; | | Ctrl+Shift+Alt+. | ctrl+shift+alt+[Period] | null | ctrl+shift+alt+[Period] | NO | +| Alt+Period | . | Alt+. | | Option+. | alt+[Period] | null | alt+[Period] | NO | +| Ctrl+Alt+Period | … | Ctrl+Alt+. | | Ctrl+Option+. | ctrl+alt+[Period] | null | ctrl+alt+[Period] | NO | +| Shift+Alt+Period | : | Shift+Alt+; | | Shift+Option+. | shift+alt+[Period] | null | shift+alt+[Period] | NO | +| Ctrl+Shift+Alt+Period | ÷ | Ctrl+Shift+Alt+; | | Ctrl+Shift+Option+. | ctrl+shift+alt+[Period] | null | ctrl+shift+alt+[Period] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Slash | - | - | | - | - | - | [Slash] | | | Ctrl+Slash | - | Ctrl+- | | Ctrl+- | ctrl+- | Ctrl+- | ctrl+[Slash] | | | Shift+Slash | _ | Shift+- | | Shift+- | shift+- | Shift+- | shift+[Slash] | | | Ctrl+Shift+Slash | _ | Ctrl+Shift+- | | Ctrl+Shift+- | ctrl+shift+- | Ctrl+Shift+- | ctrl+shift+[Slash] | | -| Alt+Slash | - | Alt+- | | Alt+- | alt+- | Alt+- | alt+[Slash] | | -| Ctrl+Alt+Slash | – | Ctrl+Alt+- | | Ctrl+Alt+- | ctrl+alt+- | Ctrl+Alt+- | ctrl+alt+[Slash] | | -| Shift+Alt+Slash | _ | Shift+Alt+- | | Shift+Alt+- | shift+alt+- | Shift+Alt+- | shift+alt+[Slash] | | -| Ctrl+Shift+Alt+Slash | — | Ctrl+Shift+Alt+- | | Ctrl+Shift+Alt+- | ctrl+shift+alt+- | Ctrl+Shift+Alt+- | ctrl+shift+alt+[Slash] | | +| Alt+Slash | - | Alt+- | | Option+- | alt+- | Alt+- | alt+[Slash] | | +| Ctrl+Alt+Slash | – | Ctrl+Alt+- | | Ctrl+Option+- | ctrl+alt+- | Ctrl+Alt+- | ctrl+alt+[Slash] | | +| Shift+Alt+Slash | _ | Shift+Alt+- | | Shift+Option+- | shift+alt+- | Shift+Alt+- | shift+alt+[Slash] | | +| Ctrl+Shift+Alt+Slash | — | Ctrl+Shift+Alt+- | | Ctrl+Shift+Option+- | ctrl+shift+alt+- | Ctrl+Shift+Alt+- | ctrl+shift+alt+[Slash] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -484,28 +484,28 @@ isUSStandard: false | Ctrl+ArrowUp | --- | Ctrl+UpArrow | | Ctrl+UpArrow | ctrl+up | Ctrl+Up | ctrl+[ArrowUp] | | | Shift+ArrowUp | --- | Shift+UpArrow | | Shift+UpArrow | shift+up | Shift+Up | shift+[ArrowUp] | | | Ctrl+Shift+ArrowUp | --- | Ctrl+Shift+UpArrow | | Ctrl+Shift+UpArrow | ctrl+shift+up | Ctrl+Shift+Up | ctrl+shift+[ArrowUp] | | -| Alt+ArrowUp | --- | Alt+UpArrow | | Alt+UpArrow | alt+up | Alt+Up | alt+[ArrowUp] | | -| Ctrl+Alt+ArrowUp | --- | Ctrl+Alt+UpArrow | | Ctrl+Alt+UpArrow | ctrl+alt+up | Ctrl+Alt+Up | ctrl+alt+[ArrowUp] | | -| Shift+Alt+ArrowUp | --- | Shift+Alt+UpArrow | | Shift+Alt+UpArrow | shift+alt+up | Shift+Alt+Up | shift+alt+[ArrowUp] | | -| Ctrl+Shift+Alt+ArrowUp | --- | Ctrl+Shift+Alt+UpArrow | | Ctrl+Shift+Alt+UpArrow | ctrl+shift+alt+up | Ctrl+Shift+Alt+Up | ctrl+shift+alt+[ArrowUp] | | +| Alt+ArrowUp | --- | Alt+UpArrow | | Option+UpArrow | alt+up | Alt+Up | alt+[ArrowUp] | | +| Ctrl+Alt+ArrowUp | --- | Ctrl+Alt+UpArrow | | Ctrl+Option+UpArrow | ctrl+alt+up | Ctrl+Alt+Up | ctrl+alt+[ArrowUp] | | +| Shift+Alt+ArrowUp | --- | Shift+Alt+UpArrow | | Shift+Option+UpArrow | shift+alt+up | Shift+Alt+Up | shift+alt+[ArrowUp] | | +| Ctrl+Shift+Alt+ArrowUp | --- | Ctrl+Shift+Alt+UpArrow | | Ctrl+Shift+Option+UpArrow | ctrl+shift+alt+up | Ctrl+Shift+Alt+Up | ctrl+shift+alt+[ArrowUp] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Numpad0 | --- | NumPad0 | | NumPad0 | numpad0 | null | [Numpad0] | | | Ctrl+Numpad0 | --- | Ctrl+NumPad0 | | Ctrl+NumPad0 | ctrl+numpad0 | null | ctrl+[Numpad0] | | | Shift+Numpad0 | --- | Shift+NumPad0 | | Shift+NumPad0 | shift+numpad0 | null | shift+[Numpad0] | | | Ctrl+Shift+Numpad0 | --- | Ctrl+Shift+NumPad0 | | Ctrl+Shift+NumPad0 | ctrl+shift+numpad0 | null | ctrl+shift+[Numpad0] | | -| Alt+Numpad0 | --- | Alt+NumPad0 | | Alt+NumPad0 | alt+numpad0 | null | alt+[Numpad0] | | -| Ctrl+Alt+Numpad0 | --- | Ctrl+Alt+NumPad0 | | Ctrl+Alt+NumPad0 | ctrl+alt+numpad0 | null | ctrl+alt+[Numpad0] | | -| Shift+Alt+Numpad0 | --- | Shift+Alt+NumPad0 | | Shift+Alt+NumPad0 | shift+alt+numpad0 | null | shift+alt+[Numpad0] | | -| Ctrl+Shift+Alt+Numpad0 | --- | Ctrl+Shift+Alt+NumPad0 | | Ctrl+Shift+Alt+NumPad0 | ctrl+shift+alt+numpad0 | null | ctrl+shift+alt+[Numpad0] | | +| Alt+Numpad0 | --- | Alt+NumPad0 | | Option+NumPad0 | alt+numpad0 | null | alt+[Numpad0] | | +| Ctrl+Alt+Numpad0 | --- | Ctrl+Alt+NumPad0 | | Ctrl+Option+NumPad0 | ctrl+alt+numpad0 | null | ctrl+alt+[Numpad0] | | +| Shift+Alt+Numpad0 | --- | Shift+Alt+NumPad0 | | Shift+Option+NumPad0 | shift+alt+numpad0 | null | shift+alt+[Numpad0] | | +| Ctrl+Shift+Alt+Numpad0 | --- | Ctrl+Shift+Alt+NumPad0 | | Ctrl+Shift+Option+NumPad0 | ctrl+shift+alt+numpad0 | null | ctrl+shift+alt+[Numpad0] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | IntlBackslash | § | | | § | [IntlBackslash] | null | [IntlBackslash] | NO | | Ctrl+IntlBackslash | § | | | Ctrl+§ | ctrl+[IntlBackslash] | null | ctrl+[IntlBackslash] | NO | | Shift+IntlBackslash | ° | | | Shift+§ | shift+[IntlBackslash] | null | shift+[IntlBackslash] | NO | | Ctrl+Shift+IntlBackslash | ° | | | Ctrl+Shift+§ | ctrl+shift+[IntlBackslash] | null | ctrl+shift+[IntlBackslash] | NO | -| Alt+IntlBackslash | § | | | Alt+§ | alt+[IntlBackslash] | null | alt+[IntlBackslash] | NO | -| Ctrl+Alt+IntlBackslash | fi | | | Ctrl+Alt+§ | ctrl+alt+[IntlBackslash] | null | ctrl+alt+[IntlBackslash] | NO | -| Shift+Alt+IntlBackslash | ° | | | Shift+Alt+§ | shift+alt+[IntlBackslash] | null | shift+alt+[IntlBackslash] | NO | -| Ctrl+Shift+Alt+IntlBackslash | ‰ | | | Ctrl+Shift+Alt+§ | ctrl+shift+alt+[IntlBackslash] | null | ctrl+shift+alt+[IntlBackslash] | NO | +| Alt+IntlBackslash | § | | | Option+§ | alt+[IntlBackslash] | null | alt+[IntlBackslash] | NO | +| Ctrl+Alt+IntlBackslash | fi | | | Ctrl+Option+§ | ctrl+alt+[IntlBackslash] | null | ctrl+alt+[IntlBackslash] | NO | +| Shift+Alt+IntlBackslash | ° | | | Shift+Option+§ | shift+alt+[IntlBackslash] | null | shift+alt+[IntlBackslash] | NO | +| Ctrl+Shift+Alt+IntlBackslash | ‰ | | | Ctrl+Shift+Option+§ | ctrl+shift+alt+[IntlBackslash] | null | ctrl+shift+alt+[IntlBackslash] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | IntlRo | --- | | | null | [IntlRo] | null | [IntlRo] | NO | | Ctrl+IntlRo | --- | | | null | ctrl+[IntlRo] | null | ctrl+[IntlRo] | NO | diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/mac_en_us.txt b/src/vs/workbench/services/keybinding/test/electron-browser/mac_en_us.txt index 7a83477293f..833fdf61c32 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/mac_en_us.txt +++ b/src/vs/workbench/services/keybinding/test/electron-browser/mac_en_us.txt @@ -6,37 +6,37 @@ isUSStandard: true | Ctrl+KeyA | a | Ctrl+A | | Ctrl+A | ctrl+a | Ctrl+A | ctrl+[KeyA] | | | Shift+KeyA | A | Shift+A | | Shift+A | shift+a | Shift+A | shift+[KeyA] | | | Ctrl+Shift+KeyA | A | Ctrl+Shift+A | | Ctrl+Shift+A | ctrl+shift+a | Ctrl+Shift+A | ctrl+shift+[KeyA] | | -| Alt+KeyA | a | Alt+A | | Alt+A | alt+a | Alt+A | alt+[KeyA] | | -| Ctrl+Alt+KeyA | å | Ctrl+Alt+A | | Ctrl+Alt+A | ctrl+alt+a | Ctrl+Alt+A | ctrl+alt+[KeyA] | | -| Shift+Alt+KeyA | A | Shift+Alt+A | | Shift+Alt+A | shift+alt+a | Shift+Alt+A | shift+alt+[KeyA] | | -| Ctrl+Shift+Alt+KeyA | Å | Ctrl+Shift+Alt+A | | Ctrl+Shift+Alt+A | ctrl+shift+alt+a | Ctrl+Shift+Alt+A | ctrl+shift+alt+[KeyA] | | +| Alt+KeyA | a | Alt+A | | Option+A | alt+a | Alt+A | alt+[KeyA] | | +| Ctrl+Alt+KeyA | å | Ctrl+Alt+A | | Ctrl+Option+A | ctrl+alt+a | Ctrl+Alt+A | ctrl+alt+[KeyA] | | +| Shift+Alt+KeyA | A | Shift+Alt+A | | Shift+Option+A | shift+alt+a | Shift+Alt+A | shift+alt+[KeyA] | | +| Ctrl+Shift+Alt+KeyA | Å | Ctrl+Shift+Alt+A | | Ctrl+Shift+Option+A | ctrl+shift+alt+a | Ctrl+Shift+Alt+A | ctrl+shift+alt+[KeyA] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyB | b | B | | B | b | B | [KeyB] | | | Ctrl+KeyB | b | Ctrl+B | | Ctrl+B | ctrl+b | Ctrl+B | ctrl+[KeyB] | | | Shift+KeyB | B | Shift+B | | Shift+B | shift+b | Shift+B | shift+[KeyB] | | | Ctrl+Shift+KeyB | B | Ctrl+Shift+B | | Ctrl+Shift+B | ctrl+shift+b | Ctrl+Shift+B | ctrl+shift+[KeyB] | | -| Alt+KeyB | b | Alt+B | | Alt+B | alt+b | Alt+B | alt+[KeyB] | | -| Ctrl+Alt+KeyB | ∫ | Ctrl+Alt+B | | Ctrl+Alt+B | ctrl+alt+b | Ctrl+Alt+B | ctrl+alt+[KeyB] | | -| Shift+Alt+KeyB | B | Shift+Alt+B | | Shift+Alt+B | shift+alt+b | Shift+Alt+B | shift+alt+[KeyB] | | -| Ctrl+Shift+Alt+KeyB | ı | Ctrl+Shift+Alt+B | | Ctrl+Shift+Alt+B | ctrl+shift+alt+b | Ctrl+Shift+Alt+B | ctrl+shift+alt+[KeyB] | | +| Alt+KeyB | b | Alt+B | | Option+B | alt+b | Alt+B | alt+[KeyB] | | +| Ctrl+Alt+KeyB | ∫ | Ctrl+Alt+B | | Ctrl+Option+B | ctrl+alt+b | Ctrl+Alt+B | ctrl+alt+[KeyB] | | +| Shift+Alt+KeyB | B | Shift+Alt+B | | Shift+Option+B | shift+alt+b | Shift+Alt+B | shift+alt+[KeyB] | | +| Ctrl+Shift+Alt+KeyB | ı | Ctrl+Shift+Alt+B | | Ctrl+Shift+Option+B | ctrl+shift+alt+b | Ctrl+Shift+Alt+B | ctrl+shift+alt+[KeyB] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyC | c | C | | C | c | C | [KeyC] | | | Ctrl+KeyC | c | Ctrl+C | | Ctrl+C | ctrl+c | Ctrl+C | ctrl+[KeyC] | | | Shift+KeyC | C | Shift+C | | Shift+C | shift+c | Shift+C | shift+[KeyC] | | | Ctrl+Shift+KeyC | C | Ctrl+Shift+C | | Ctrl+Shift+C | ctrl+shift+c | Ctrl+Shift+C | ctrl+shift+[KeyC] | | -| Alt+KeyC | c | Alt+C | | Alt+C | alt+c | Alt+C | alt+[KeyC] | | -| Ctrl+Alt+KeyC | ç | Ctrl+Alt+C | | Ctrl+Alt+C | ctrl+alt+c | Ctrl+Alt+C | ctrl+alt+[KeyC] | | -| Shift+Alt+KeyC | C | Shift+Alt+C | | Shift+Alt+C | shift+alt+c | Shift+Alt+C | shift+alt+[KeyC] | | -| Ctrl+Shift+Alt+KeyC | Ç | Ctrl+Shift+Alt+C | | Ctrl+Shift+Alt+C | ctrl+shift+alt+c | Ctrl+Shift+Alt+C | ctrl+shift+alt+[KeyC] | | +| Alt+KeyC | c | Alt+C | | Option+C | alt+c | Alt+C | alt+[KeyC] | | +| Ctrl+Alt+KeyC | ç | Ctrl+Alt+C | | Ctrl+Option+C | ctrl+alt+c | Ctrl+Alt+C | ctrl+alt+[KeyC] | | +| Shift+Alt+KeyC | C | Shift+Alt+C | | Shift+Option+C | shift+alt+c | Shift+Alt+C | shift+alt+[KeyC] | | +| Ctrl+Shift+Alt+KeyC | Ç | Ctrl+Shift+Alt+C | | Ctrl+Shift+Option+C | ctrl+shift+alt+c | Ctrl+Shift+Alt+C | ctrl+shift+alt+[KeyC] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyD | d | D | | D | d | D | [KeyD] | | | Ctrl+KeyD | d | Ctrl+D | | Ctrl+D | ctrl+d | Ctrl+D | ctrl+[KeyD] | | | Shift+KeyD | D | Shift+D | | Shift+D | shift+d | Shift+D | shift+[KeyD] | | | Ctrl+Shift+KeyD | D | Ctrl+Shift+D | | Ctrl+Shift+D | ctrl+shift+d | Ctrl+Shift+D | ctrl+shift+[KeyD] | | -| Alt+KeyD | d | Alt+D | | Alt+D | alt+d | Alt+D | alt+[KeyD] | | -| Ctrl+Alt+KeyD | ∂ | Ctrl+Alt+D | | Ctrl+Alt+D | ctrl+alt+d | Ctrl+Alt+D | ctrl+alt+[KeyD] | | -| Shift+Alt+KeyD | D | Shift+Alt+D | | Shift+Alt+D | shift+alt+d | Shift+Alt+D | shift+alt+[KeyD] | | -| Ctrl+Shift+Alt+KeyD | Î | Ctrl+Shift+Alt+D | | Ctrl+Shift+Alt+D | ctrl+shift+alt+d | Ctrl+Shift+Alt+D | ctrl+shift+alt+[KeyD] | | +| Alt+KeyD | d | Alt+D | | Option+D | alt+d | Alt+D | alt+[KeyD] | | +| Ctrl+Alt+KeyD | ∂ | Ctrl+Alt+D | | Ctrl+Option+D | ctrl+alt+d | Ctrl+Alt+D | ctrl+alt+[KeyD] | | +| Shift+Alt+KeyD | D | Shift+Alt+D | | Shift+Option+D | shift+alt+d | Shift+Alt+D | shift+alt+[KeyD] | | +| Ctrl+Shift+Alt+KeyD | Î | Ctrl+Shift+Alt+D | | Ctrl+Shift+Option+D | ctrl+shift+alt+d | Ctrl+Shift+Alt+D | ctrl+shift+alt+[KeyD] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -44,37 +44,37 @@ isUSStandard: true | Ctrl+KeyE | e | Ctrl+E | | Ctrl+E | ctrl+e | Ctrl+E | ctrl+[KeyE] | | | Shift+KeyE | E | Shift+E | | Shift+E | shift+e | Shift+E | shift+[KeyE] | | | Ctrl+Shift+KeyE | E | Ctrl+Shift+E | | Ctrl+Shift+E | ctrl+shift+e | Ctrl+Shift+E | ctrl+shift+[KeyE] | | -| Alt+KeyE | e | Alt+E | | Alt+E | alt+e | Alt+E | alt+[KeyE] | | -| Ctrl+Alt+KeyE | ´ | Ctrl+Alt+E | | Ctrl+Alt+E | ctrl+alt+e | Ctrl+Alt+E | ctrl+alt+[KeyE] | | -| Shift+Alt+KeyE | E | Shift+Alt+E | | Shift+Alt+E | shift+alt+e | Shift+Alt+E | shift+alt+[KeyE] | | -| Ctrl+Shift+Alt+KeyE | ´ | Ctrl+Shift+Alt+E | | Ctrl+Shift+Alt+E | ctrl+shift+alt+e | Ctrl+Shift+Alt+E | ctrl+shift+alt+[KeyE] | | +| Alt+KeyE | e | Alt+E | | Option+E | alt+e | Alt+E | alt+[KeyE] | | +| Ctrl+Alt+KeyE | ´ | Ctrl+Alt+E | | Ctrl+Option+E | ctrl+alt+e | Ctrl+Alt+E | ctrl+alt+[KeyE] | | +| Shift+Alt+KeyE | E | Shift+Alt+E | | Shift+Option+E | shift+alt+e | Shift+Alt+E | shift+alt+[KeyE] | | +| Ctrl+Shift+Alt+KeyE | ´ | Ctrl+Shift+Alt+E | | Ctrl+Shift+Option+E | ctrl+shift+alt+e | Ctrl+Shift+Alt+E | ctrl+shift+alt+[KeyE] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyF | f | F | | F | f | F | [KeyF] | | | Ctrl+KeyF | f | Ctrl+F | | Ctrl+F | ctrl+f | Ctrl+F | ctrl+[KeyF] | | | Shift+KeyF | F | Shift+F | | Shift+F | shift+f | Shift+F | shift+[KeyF] | | | Ctrl+Shift+KeyF | F | Ctrl+Shift+F | | Ctrl+Shift+F | ctrl+shift+f | Ctrl+Shift+F | ctrl+shift+[KeyF] | | -| Alt+KeyF | f | Alt+F | | Alt+F | alt+f | Alt+F | alt+[KeyF] | | -| Ctrl+Alt+KeyF | ƒ | Ctrl+Alt+F | | Ctrl+Alt+F | ctrl+alt+f | Ctrl+Alt+F | ctrl+alt+[KeyF] | | -| Shift+Alt+KeyF | F | Shift+Alt+F | | Shift+Alt+F | shift+alt+f | Shift+Alt+F | shift+alt+[KeyF] | | -| Ctrl+Shift+Alt+KeyF | Ï | Ctrl+Shift+Alt+F | | Ctrl+Shift+Alt+F | ctrl+shift+alt+f | Ctrl+Shift+Alt+F | ctrl+shift+alt+[KeyF] | | +| Alt+KeyF | f | Alt+F | | Option+F | alt+f | Alt+F | alt+[KeyF] | | +| Ctrl+Alt+KeyF | ƒ | Ctrl+Alt+F | | Ctrl+Option+F | ctrl+alt+f | Ctrl+Alt+F | ctrl+alt+[KeyF] | | +| Shift+Alt+KeyF | F | Shift+Alt+F | | Shift+Option+F | shift+alt+f | Shift+Alt+F | shift+alt+[KeyF] | | +| Ctrl+Shift+Alt+KeyF | Ï | Ctrl+Shift+Alt+F | | Ctrl+Shift+Option+F | ctrl+shift+alt+f | Ctrl+Shift+Alt+F | ctrl+shift+alt+[KeyF] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyG | g | G | | G | g | G | [KeyG] | | | Ctrl+KeyG | g | Ctrl+G | | Ctrl+G | ctrl+g | Ctrl+G | ctrl+[KeyG] | | | Shift+KeyG | G | Shift+G | | Shift+G | shift+g | Shift+G | shift+[KeyG] | | | Ctrl+Shift+KeyG | G | Ctrl+Shift+G | | Ctrl+Shift+G | ctrl+shift+g | Ctrl+Shift+G | ctrl+shift+[KeyG] | | -| Alt+KeyG | g | Alt+G | | Alt+G | alt+g | Alt+G | alt+[KeyG] | | -| Ctrl+Alt+KeyG | © | Ctrl+Alt+G | | Ctrl+Alt+G | ctrl+alt+g | Ctrl+Alt+G | ctrl+alt+[KeyG] | | -| Shift+Alt+KeyG | G | Shift+Alt+G | | Shift+Alt+G | shift+alt+g | Shift+Alt+G | shift+alt+[KeyG] | | -| Ctrl+Shift+Alt+KeyG | ˝ | Ctrl+Shift+Alt+G | | Ctrl+Shift+Alt+G | ctrl+shift+alt+g | Ctrl+Shift+Alt+G | ctrl+shift+alt+[KeyG] | | +| Alt+KeyG | g | Alt+G | | Option+G | alt+g | Alt+G | alt+[KeyG] | | +| Ctrl+Alt+KeyG | © | Ctrl+Alt+G | | Ctrl+Option+G | ctrl+alt+g | Ctrl+Alt+G | ctrl+alt+[KeyG] | | +| Shift+Alt+KeyG | G | Shift+Alt+G | | Shift+Option+G | shift+alt+g | Shift+Alt+G | shift+alt+[KeyG] | | +| Ctrl+Shift+Alt+KeyG | ˝ | Ctrl+Shift+Alt+G | | Ctrl+Shift+Option+G | ctrl+shift+alt+g | Ctrl+Shift+Alt+G | ctrl+shift+alt+[KeyG] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyH | h | H | | H | h | H | [KeyH] | | | Ctrl+KeyH | h | Ctrl+H | | Ctrl+H | ctrl+h | Ctrl+H | ctrl+[KeyH] | | | Shift+KeyH | H | Shift+H | | Shift+H | shift+h | Shift+H | shift+[KeyH] | | | Ctrl+Shift+KeyH | H | Ctrl+Shift+H | | Ctrl+Shift+H | ctrl+shift+h | Ctrl+Shift+H | ctrl+shift+[KeyH] | | -| Alt+KeyH | h | Alt+H | | Alt+H | alt+h | Alt+H | alt+[KeyH] | | -| Ctrl+Alt+KeyH | ˙ | Ctrl+Alt+H | | Ctrl+Alt+H | ctrl+alt+h | Ctrl+Alt+H | ctrl+alt+[KeyH] | | -| Shift+Alt+KeyH | H | Shift+Alt+H | | Shift+Alt+H | shift+alt+h | Shift+Alt+H | shift+alt+[KeyH] | | -| Ctrl+Shift+Alt+KeyH | Ó | Ctrl+Shift+Alt+H | | Ctrl+Shift+Alt+H | ctrl+shift+alt+h | Ctrl+Shift+Alt+H | ctrl+shift+alt+[KeyH] | | +| Alt+KeyH | h | Alt+H | | Option+H | alt+h | Alt+H | alt+[KeyH] | | +| Ctrl+Alt+KeyH | ˙ | Ctrl+Alt+H | | Ctrl+Option+H | ctrl+alt+h | Ctrl+Alt+H | ctrl+alt+[KeyH] | | +| Shift+Alt+KeyH | H | Shift+Alt+H | | Shift+Option+H | shift+alt+h | Shift+Alt+H | shift+alt+[KeyH] | | +| Ctrl+Shift+Alt+KeyH | Ó | Ctrl+Shift+Alt+H | | Ctrl+Shift+Option+H | ctrl+shift+alt+h | Ctrl+Shift+Alt+H | ctrl+shift+alt+[KeyH] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -82,37 +82,37 @@ isUSStandard: true | Ctrl+KeyI | i | Ctrl+I | | Ctrl+I | ctrl+i | Ctrl+I | ctrl+[KeyI] | | | Shift+KeyI | I | Shift+I | | Shift+I | shift+i | Shift+I | shift+[KeyI] | | | Ctrl+Shift+KeyI | I | Ctrl+Shift+I | | Ctrl+Shift+I | ctrl+shift+i | Ctrl+Shift+I | ctrl+shift+[KeyI] | | -| Alt+KeyI | i | Alt+I | | Alt+I | alt+i | Alt+I | alt+[KeyI] | | -| Ctrl+Alt+KeyI | ˆ | Ctrl+Alt+I | | Ctrl+Alt+I | ctrl+alt+i | Ctrl+Alt+I | ctrl+alt+[KeyI] | | -| Shift+Alt+KeyI | I | Shift+Alt+I | | Shift+Alt+I | shift+alt+i | Shift+Alt+I | shift+alt+[KeyI] | | -| Ctrl+Shift+Alt+KeyI | ˆ | Ctrl+Shift+Alt+I | | Ctrl+Shift+Alt+I | ctrl+shift+alt+i | Ctrl+Shift+Alt+I | ctrl+shift+alt+[KeyI] | | +| Alt+KeyI | i | Alt+I | | Option+I | alt+i | Alt+I | alt+[KeyI] | | +| Ctrl+Alt+KeyI | ˆ | Ctrl+Alt+I | | Ctrl+Option+I | ctrl+alt+i | Ctrl+Alt+I | ctrl+alt+[KeyI] | | +| Shift+Alt+KeyI | I | Shift+Alt+I | | Shift+Option+I | shift+alt+i | Shift+Alt+I | shift+alt+[KeyI] | | +| Ctrl+Shift+Alt+KeyI | ˆ | Ctrl+Shift+Alt+I | | Ctrl+Shift+Option+I | ctrl+shift+alt+i | Ctrl+Shift+Alt+I | ctrl+shift+alt+[KeyI] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyJ | j | J | | J | j | J | [KeyJ] | | | Ctrl+KeyJ | j | Ctrl+J | | Ctrl+J | ctrl+j | Ctrl+J | ctrl+[KeyJ] | | | Shift+KeyJ | J | Shift+J | | Shift+J | shift+j | Shift+J | shift+[KeyJ] | | | Ctrl+Shift+KeyJ | J | Ctrl+Shift+J | | Ctrl+Shift+J | ctrl+shift+j | Ctrl+Shift+J | ctrl+shift+[KeyJ] | | -| Alt+KeyJ | j | Alt+J | | Alt+J | alt+j | Alt+J | alt+[KeyJ] | | -| Ctrl+Alt+KeyJ | ∆ | Ctrl+Alt+J | | Ctrl+Alt+J | ctrl+alt+j | Ctrl+Alt+J | ctrl+alt+[KeyJ] | | -| Shift+Alt+KeyJ | J | Shift+Alt+J | | Shift+Alt+J | shift+alt+j | Shift+Alt+J | shift+alt+[KeyJ] | | -| Ctrl+Shift+Alt+KeyJ | Ô | Ctrl+Shift+Alt+J | | Ctrl+Shift+Alt+J | ctrl+shift+alt+j | Ctrl+Shift+Alt+J | ctrl+shift+alt+[KeyJ] | | +| Alt+KeyJ | j | Alt+J | | Option+J | alt+j | Alt+J | alt+[KeyJ] | | +| Ctrl+Alt+KeyJ | ∆ | Ctrl+Alt+J | | Ctrl+Option+J | ctrl+alt+j | Ctrl+Alt+J | ctrl+alt+[KeyJ] | | +| Shift+Alt+KeyJ | J | Shift+Alt+J | | Shift+Option+J | shift+alt+j | Shift+Alt+J | shift+alt+[KeyJ] | | +| Ctrl+Shift+Alt+KeyJ | Ô | Ctrl+Shift+Alt+J | | Ctrl+Shift+Option+J | ctrl+shift+alt+j | Ctrl+Shift+Alt+J | ctrl+shift+alt+[KeyJ] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyK | k | K | | K | k | K | [KeyK] | | | Ctrl+KeyK | k | Ctrl+K | | Ctrl+K | ctrl+k | Ctrl+K | ctrl+[KeyK] | | | Shift+KeyK | K | Shift+K | | Shift+K | shift+k | Shift+K | shift+[KeyK] | | | Ctrl+Shift+KeyK | K | Ctrl+Shift+K | | Ctrl+Shift+K | ctrl+shift+k | Ctrl+Shift+K | ctrl+shift+[KeyK] | | -| Alt+KeyK | k | Alt+K | | Alt+K | alt+k | Alt+K | alt+[KeyK] | | -| Ctrl+Alt+KeyK | ˚ | Ctrl+Alt+K | | Ctrl+Alt+K | ctrl+alt+k | Ctrl+Alt+K | ctrl+alt+[KeyK] | | -| Shift+Alt+KeyK | K | Shift+Alt+K | | Shift+Alt+K | shift+alt+k | Shift+Alt+K | shift+alt+[KeyK] | | -| Ctrl+Shift+Alt+KeyK |  | Ctrl+Shift+Alt+K | | Ctrl+Shift+Alt+K | ctrl+shift+alt+k | Ctrl+Shift+Alt+K | ctrl+shift+alt+[KeyK] | | +| Alt+KeyK | k | Alt+K | | Option+K | alt+k | Alt+K | alt+[KeyK] | | +| Ctrl+Alt+KeyK | ˚ | Ctrl+Alt+K | | Ctrl+Option+K | ctrl+alt+k | Ctrl+Alt+K | ctrl+alt+[KeyK] | | +| Shift+Alt+KeyK | K | Shift+Alt+K | | Shift+Option+K | shift+alt+k | Shift+Alt+K | shift+alt+[KeyK] | | +| Ctrl+Shift+Alt+KeyK |  | Ctrl+Shift+Alt+K | | Ctrl+Shift+Option+K | ctrl+shift+alt+k | Ctrl+Shift+Alt+K | ctrl+shift+alt+[KeyK] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyL | l | L | | L | l | L | [KeyL] | | | Ctrl+KeyL | l | Ctrl+L | | Ctrl+L | ctrl+l | Ctrl+L | ctrl+[KeyL] | | | Shift+KeyL | L | Shift+L | | Shift+L | shift+l | Shift+L | shift+[KeyL] | | | Ctrl+Shift+KeyL | L | Ctrl+Shift+L | | Ctrl+Shift+L | ctrl+shift+l | Ctrl+Shift+L | ctrl+shift+[KeyL] | | -| Alt+KeyL | l | Alt+L | | Alt+L | alt+l | Alt+L | alt+[KeyL] | | -| Ctrl+Alt+KeyL | ¬ | Ctrl+Alt+L | | Ctrl+Alt+L | ctrl+alt+l | Ctrl+Alt+L | ctrl+alt+[KeyL] | | -| Shift+Alt+KeyL | L | Shift+Alt+L | | Shift+Alt+L | shift+alt+l | Shift+Alt+L | shift+alt+[KeyL] | | -| Ctrl+Shift+Alt+KeyL | Ò | Ctrl+Shift+Alt+L | | Ctrl+Shift+Alt+L | ctrl+shift+alt+l | Ctrl+Shift+Alt+L | ctrl+shift+alt+[KeyL] | | +| Alt+KeyL | l | Alt+L | | Option+L | alt+l | Alt+L | alt+[KeyL] | | +| Ctrl+Alt+KeyL | ¬ | Ctrl+Alt+L | | Ctrl+Option+L | ctrl+alt+l | Ctrl+Alt+L | ctrl+alt+[KeyL] | | +| Shift+Alt+KeyL | L | Shift+Alt+L | | Shift+Option+L | shift+alt+l | Shift+Alt+L | shift+alt+[KeyL] | | +| Ctrl+Shift+Alt+KeyL | Ò | Ctrl+Shift+Alt+L | | Ctrl+Shift+Option+L | ctrl+shift+alt+l | Ctrl+Shift+Alt+L | ctrl+shift+alt+[KeyL] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -120,37 +120,37 @@ isUSStandard: true | Ctrl+KeyM | m | Ctrl+M | | Ctrl+M | ctrl+m | Ctrl+M | ctrl+[KeyM] | | | Shift+KeyM | M | Shift+M | | Shift+M | shift+m | Shift+M | shift+[KeyM] | | | Ctrl+Shift+KeyM | M | Ctrl+Shift+M | | Ctrl+Shift+M | ctrl+shift+m | Ctrl+Shift+M | ctrl+shift+[KeyM] | | -| Alt+KeyM | m | Alt+M | | Alt+M | alt+m | Alt+M | alt+[KeyM] | | -| Ctrl+Alt+KeyM | µ | Ctrl+Alt+M | | Ctrl+Alt+M | ctrl+alt+m | Ctrl+Alt+M | ctrl+alt+[KeyM] | | -| Shift+Alt+KeyM | M | Shift+Alt+M | | Shift+Alt+M | shift+alt+m | Shift+Alt+M | shift+alt+[KeyM] | | -| Ctrl+Shift+Alt+KeyM |  | Ctrl+Shift+Alt+M | | Ctrl+Shift+Alt+M | ctrl+shift+alt+m | Ctrl+Shift+Alt+M | ctrl+shift+alt+[KeyM] | | +| Alt+KeyM | m | Alt+M | | Option+M | alt+m | Alt+M | alt+[KeyM] | | +| Ctrl+Alt+KeyM | µ | Ctrl+Alt+M | | Ctrl+Option+M | ctrl+alt+m | Ctrl+Alt+M | ctrl+alt+[KeyM] | | +| Shift+Alt+KeyM | M | Shift+Alt+M | | Shift+Option+M | shift+alt+m | Shift+Alt+M | shift+alt+[KeyM] | | +| Ctrl+Shift+Alt+KeyM |  | Ctrl+Shift+Alt+M | | Ctrl+Shift+Option+M | ctrl+shift+alt+m | Ctrl+Shift+Alt+M | ctrl+shift+alt+[KeyM] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyN | n | N | | N | n | N | [KeyN] | | | Ctrl+KeyN | n | Ctrl+N | | Ctrl+N | ctrl+n | Ctrl+N | ctrl+[KeyN] | | | Shift+KeyN | N | Shift+N | | Shift+N | shift+n | Shift+N | shift+[KeyN] | | | Ctrl+Shift+KeyN | N | Ctrl+Shift+N | | Ctrl+Shift+N | ctrl+shift+n | Ctrl+Shift+N | ctrl+shift+[KeyN] | | -| Alt+KeyN | n | Alt+N | | Alt+N | alt+n | Alt+N | alt+[KeyN] | | -| Ctrl+Alt+KeyN | ˜ | Ctrl+Alt+N | | Ctrl+Alt+N | ctrl+alt+n | Ctrl+Alt+N | ctrl+alt+[KeyN] | | -| Shift+Alt+KeyN | N | Shift+Alt+N | | Shift+Alt+N | shift+alt+n | Shift+Alt+N | shift+alt+[KeyN] | | -| Ctrl+Shift+Alt+KeyN | ˜ | Ctrl+Shift+Alt+N | | Ctrl+Shift+Alt+N | ctrl+shift+alt+n | Ctrl+Shift+Alt+N | ctrl+shift+alt+[KeyN] | | +| Alt+KeyN | n | Alt+N | | Option+N | alt+n | Alt+N | alt+[KeyN] | | +| Ctrl+Alt+KeyN | ˜ | Ctrl+Alt+N | | Ctrl+Option+N | ctrl+alt+n | Ctrl+Alt+N | ctrl+alt+[KeyN] | | +| Shift+Alt+KeyN | N | Shift+Alt+N | | Shift+Option+N | shift+alt+n | Shift+Alt+N | shift+alt+[KeyN] | | +| Ctrl+Shift+Alt+KeyN | ˜ | Ctrl+Shift+Alt+N | | Ctrl+Shift+Option+N | ctrl+shift+alt+n | Ctrl+Shift+Alt+N | ctrl+shift+alt+[KeyN] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyO | o | O | | O | o | O | [KeyO] | | | Ctrl+KeyO | o | Ctrl+O | | Ctrl+O | ctrl+o | Ctrl+O | ctrl+[KeyO] | | | Shift+KeyO | O | Shift+O | | Shift+O | shift+o | Shift+O | shift+[KeyO] | | | Ctrl+Shift+KeyO | O | Ctrl+Shift+O | | Ctrl+Shift+O | ctrl+shift+o | Ctrl+Shift+O | ctrl+shift+[KeyO] | | -| Alt+KeyO | o | Alt+O | | Alt+O | alt+o | Alt+O | alt+[KeyO] | | -| Ctrl+Alt+KeyO | ø | Ctrl+Alt+O | | Ctrl+Alt+O | ctrl+alt+o | Ctrl+Alt+O | ctrl+alt+[KeyO] | | -| Shift+Alt+KeyO | O | Shift+Alt+O | | Shift+Alt+O | shift+alt+o | Shift+Alt+O | shift+alt+[KeyO] | | -| Ctrl+Shift+Alt+KeyO | Ø | Ctrl+Shift+Alt+O | | Ctrl+Shift+Alt+O | ctrl+shift+alt+o | Ctrl+Shift+Alt+O | ctrl+shift+alt+[KeyO] | | +| Alt+KeyO | o | Alt+O | | Option+O | alt+o | Alt+O | alt+[KeyO] | | +| Ctrl+Alt+KeyO | ø | Ctrl+Alt+O | | Ctrl+Option+O | ctrl+alt+o | Ctrl+Alt+O | ctrl+alt+[KeyO] | | +| Shift+Alt+KeyO | O | Shift+Alt+O | | Shift+Option+O | shift+alt+o | Shift+Alt+O | shift+alt+[KeyO] | | +| Ctrl+Shift+Alt+KeyO | Ø | Ctrl+Shift+Alt+O | | Ctrl+Shift+Option+O | ctrl+shift+alt+o | Ctrl+Shift+Alt+O | ctrl+shift+alt+[KeyO] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyP | p | P | | P | p | P | [KeyP] | | | Ctrl+KeyP | p | Ctrl+P | | Ctrl+P | ctrl+p | Ctrl+P | ctrl+[KeyP] | | | Shift+KeyP | P | Shift+P | | Shift+P | shift+p | Shift+P | shift+[KeyP] | | | Ctrl+Shift+KeyP | P | Ctrl+Shift+P | | Ctrl+Shift+P | ctrl+shift+p | Ctrl+Shift+P | ctrl+shift+[KeyP] | | -| Alt+KeyP | p | Alt+P | | Alt+P | alt+p | Alt+P | alt+[KeyP] | | -| Ctrl+Alt+KeyP | π | Ctrl+Alt+P | | Ctrl+Alt+P | ctrl+alt+p | Ctrl+Alt+P | ctrl+alt+[KeyP] | | -| Shift+Alt+KeyP | P | Shift+Alt+P | | Shift+Alt+P | shift+alt+p | Shift+Alt+P | shift+alt+[KeyP] | | -| Ctrl+Shift+Alt+KeyP | ∏ | Ctrl+Shift+Alt+P | | Ctrl+Shift+Alt+P | ctrl+shift+alt+p | Ctrl+Shift+Alt+P | ctrl+shift+alt+[KeyP] | | +| Alt+KeyP | p | Alt+P | | Option+P | alt+p | Alt+P | alt+[KeyP] | | +| Ctrl+Alt+KeyP | π | Ctrl+Alt+P | | Ctrl+Option+P | ctrl+alt+p | Ctrl+Alt+P | ctrl+alt+[KeyP] | | +| Shift+Alt+KeyP | P | Shift+Alt+P | | Shift+Option+P | shift+alt+p | Shift+Alt+P | shift+alt+[KeyP] | | +| Ctrl+Shift+Alt+KeyP | ∏ | Ctrl+Shift+Alt+P | | Ctrl+Shift+Option+P | ctrl+shift+alt+p | Ctrl+Shift+Alt+P | ctrl+shift+alt+[KeyP] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -158,37 +158,37 @@ isUSStandard: true | Ctrl+KeyQ | q | Ctrl+Q | | Ctrl+Q | ctrl+q | Ctrl+Q | ctrl+[KeyQ] | | | Shift+KeyQ | Q | Shift+Q | | Shift+Q | shift+q | Shift+Q | shift+[KeyQ] | | | Ctrl+Shift+KeyQ | Q | Ctrl+Shift+Q | | Ctrl+Shift+Q | ctrl+shift+q | Ctrl+Shift+Q | ctrl+shift+[KeyQ] | | -| Alt+KeyQ | q | Alt+Q | | Alt+Q | alt+q | Alt+Q | alt+[KeyQ] | | -| Ctrl+Alt+KeyQ | œ | Ctrl+Alt+Q | | Ctrl+Alt+Q | ctrl+alt+q | Ctrl+Alt+Q | ctrl+alt+[KeyQ] | | -| Shift+Alt+KeyQ | Q | Shift+Alt+Q | | Shift+Alt+Q | shift+alt+q | Shift+Alt+Q | shift+alt+[KeyQ] | | -| Ctrl+Shift+Alt+KeyQ | Œ | Ctrl+Shift+Alt+Q | | Ctrl+Shift+Alt+Q | ctrl+shift+alt+q | Ctrl+Shift+Alt+Q | ctrl+shift+alt+[KeyQ] | | +| Alt+KeyQ | q | Alt+Q | | Option+Q | alt+q | Alt+Q | alt+[KeyQ] | | +| Ctrl+Alt+KeyQ | œ | Ctrl+Alt+Q | | Ctrl+Option+Q | ctrl+alt+q | Ctrl+Alt+Q | ctrl+alt+[KeyQ] | | +| Shift+Alt+KeyQ | Q | Shift+Alt+Q | | Shift+Option+Q | shift+alt+q | Shift+Alt+Q | shift+alt+[KeyQ] | | +| Ctrl+Shift+Alt+KeyQ | Œ | Ctrl+Shift+Alt+Q | | Ctrl+Shift+Option+Q | ctrl+shift+alt+q | Ctrl+Shift+Alt+Q | ctrl+shift+alt+[KeyQ] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyR | r | R | | R | r | R | [KeyR] | | | Ctrl+KeyR | r | Ctrl+R | | Ctrl+R | ctrl+r | Ctrl+R | ctrl+[KeyR] | | | Shift+KeyR | R | Shift+R | | Shift+R | shift+r | Shift+R | shift+[KeyR] | | | Ctrl+Shift+KeyR | R | Ctrl+Shift+R | | Ctrl+Shift+R | ctrl+shift+r | Ctrl+Shift+R | ctrl+shift+[KeyR] | | -| Alt+KeyR | r | Alt+R | | Alt+R | alt+r | Alt+R | alt+[KeyR] | | -| Ctrl+Alt+KeyR | ® | Ctrl+Alt+R | | Ctrl+Alt+R | ctrl+alt+r | Ctrl+Alt+R | ctrl+alt+[KeyR] | | -| Shift+Alt+KeyR | R | Shift+Alt+R | | Shift+Alt+R | shift+alt+r | Shift+Alt+R | shift+alt+[KeyR] | | -| Ctrl+Shift+Alt+KeyR | ‰ | Ctrl+Shift+Alt+R | | Ctrl+Shift+Alt+R | ctrl+shift+alt+r | Ctrl+Shift+Alt+R | ctrl+shift+alt+[KeyR] | | +| Alt+KeyR | r | Alt+R | | Option+R | alt+r | Alt+R | alt+[KeyR] | | +| Ctrl+Alt+KeyR | ® | Ctrl+Alt+R | | Ctrl+Option+R | ctrl+alt+r | Ctrl+Alt+R | ctrl+alt+[KeyR] | | +| Shift+Alt+KeyR | R | Shift+Alt+R | | Shift+Option+R | shift+alt+r | Shift+Alt+R | shift+alt+[KeyR] | | +| Ctrl+Shift+Alt+KeyR | ‰ | Ctrl+Shift+Alt+R | | Ctrl+Shift+Option+R | ctrl+shift+alt+r | Ctrl+Shift+Alt+R | ctrl+shift+alt+[KeyR] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyS | s | S | | S | s | S | [KeyS] | | | Ctrl+KeyS | s | Ctrl+S | | Ctrl+S | ctrl+s | Ctrl+S | ctrl+[KeyS] | | | Shift+KeyS | S | Shift+S | | Shift+S | shift+s | Shift+S | shift+[KeyS] | | | Ctrl+Shift+KeyS | S | Ctrl+Shift+S | | Ctrl+Shift+S | ctrl+shift+s | Ctrl+Shift+S | ctrl+shift+[KeyS] | | -| Alt+KeyS | s | Alt+S | | Alt+S | alt+s | Alt+S | alt+[KeyS] | | -| Ctrl+Alt+KeyS | ß | Ctrl+Alt+S | | Ctrl+Alt+S | ctrl+alt+s | Ctrl+Alt+S | ctrl+alt+[KeyS] | | -| Shift+Alt+KeyS | S | Shift+Alt+S | | Shift+Alt+S | shift+alt+s | Shift+Alt+S | shift+alt+[KeyS] | | -| Ctrl+Shift+Alt+KeyS | Í | Ctrl+Shift+Alt+S | | Ctrl+Shift+Alt+S | ctrl+shift+alt+s | Ctrl+Shift+Alt+S | ctrl+shift+alt+[KeyS] | | +| Alt+KeyS | s | Alt+S | | Option+S | alt+s | Alt+S | alt+[KeyS] | | +| Ctrl+Alt+KeyS | ß | Ctrl+Alt+S | | Ctrl+Option+S | ctrl+alt+s | Ctrl+Alt+S | ctrl+alt+[KeyS] | | +| Shift+Alt+KeyS | S | Shift+Alt+S | | Shift+Option+S | shift+alt+s | Shift+Alt+S | shift+alt+[KeyS] | | +| Ctrl+Shift+Alt+KeyS | Í | Ctrl+Shift+Alt+S | | Ctrl+Shift+Option+S | ctrl+shift+alt+s | Ctrl+Shift+Alt+S | ctrl+shift+alt+[KeyS] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyT | t | T | | T | t | T | [KeyT] | | | Ctrl+KeyT | t | Ctrl+T | | Ctrl+T | ctrl+t | Ctrl+T | ctrl+[KeyT] | | | Shift+KeyT | T | Shift+T | | Shift+T | shift+t | Shift+T | shift+[KeyT] | | | Ctrl+Shift+KeyT | T | Ctrl+Shift+T | | Ctrl+Shift+T | ctrl+shift+t | Ctrl+Shift+T | ctrl+shift+[KeyT] | | -| Alt+KeyT | t | Alt+T | | Alt+T | alt+t | Alt+T | alt+[KeyT] | | -| Ctrl+Alt+KeyT | † | Ctrl+Alt+T | | Ctrl+Alt+T | ctrl+alt+t | Ctrl+Alt+T | ctrl+alt+[KeyT] | | -| Shift+Alt+KeyT | T | Shift+Alt+T | | Shift+Alt+T | shift+alt+t | Shift+Alt+T | shift+alt+[KeyT] | | -| Ctrl+Shift+Alt+KeyT | ˇ | Ctrl+Shift+Alt+T | | Ctrl+Shift+Alt+T | ctrl+shift+alt+t | Ctrl+Shift+Alt+T | ctrl+shift+alt+[KeyT] | | +| Alt+KeyT | t | Alt+T | | Option+T | alt+t | Alt+T | alt+[KeyT] | | +| Ctrl+Alt+KeyT | † | Ctrl+Alt+T | | Ctrl+Option+T | ctrl+alt+t | Ctrl+Alt+T | ctrl+alt+[KeyT] | | +| Shift+Alt+KeyT | T | Shift+Alt+T | | Shift+Option+T | shift+alt+t | Shift+Alt+T | shift+alt+[KeyT] | | +| Ctrl+Shift+Alt+KeyT | ˇ | Ctrl+Shift+Alt+T | | Ctrl+Shift+Option+T | ctrl+shift+alt+t | Ctrl+Shift+Alt+T | ctrl+shift+alt+[KeyT] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -196,37 +196,37 @@ isUSStandard: true | Ctrl+KeyU | u | Ctrl+U | | Ctrl+U | ctrl+u | Ctrl+U | ctrl+[KeyU] | | | Shift+KeyU | U | Shift+U | | Shift+U | shift+u | Shift+U | shift+[KeyU] | | | Ctrl+Shift+KeyU | U | Ctrl+Shift+U | | Ctrl+Shift+U | ctrl+shift+u | Ctrl+Shift+U | ctrl+shift+[KeyU] | | -| Alt+KeyU | u | Alt+U | | Alt+U | alt+u | Alt+U | alt+[KeyU] | | -| Ctrl+Alt+KeyU | ¨ | Ctrl+Alt+U | | Ctrl+Alt+U | ctrl+alt+u | Ctrl+Alt+U | ctrl+alt+[KeyU] | | -| Shift+Alt+KeyU | U | Shift+Alt+U | | Shift+Alt+U | shift+alt+u | Shift+Alt+U | shift+alt+[KeyU] | | -| Ctrl+Shift+Alt+KeyU | ¨ | Ctrl+Shift+Alt+U | | Ctrl+Shift+Alt+U | ctrl+shift+alt+u | Ctrl+Shift+Alt+U | ctrl+shift+alt+[KeyU] | | +| Alt+KeyU | u | Alt+U | | Option+U | alt+u | Alt+U | alt+[KeyU] | | +| Ctrl+Alt+KeyU | ¨ | Ctrl+Alt+U | | Ctrl+Option+U | ctrl+alt+u | Ctrl+Alt+U | ctrl+alt+[KeyU] | | +| Shift+Alt+KeyU | U | Shift+Alt+U | | Shift+Option+U | shift+alt+u | Shift+Alt+U | shift+alt+[KeyU] | | +| Ctrl+Shift+Alt+KeyU | ¨ | Ctrl+Shift+Alt+U | | Ctrl+Shift+Option+U | ctrl+shift+alt+u | Ctrl+Shift+Alt+U | ctrl+shift+alt+[KeyU] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyV | v | V | | V | v | V | [KeyV] | | | Ctrl+KeyV | v | Ctrl+V | | Ctrl+V | ctrl+v | Ctrl+V | ctrl+[KeyV] | | | Shift+KeyV | V | Shift+V | | Shift+V | shift+v | Shift+V | shift+[KeyV] | | | Ctrl+Shift+KeyV | V | Ctrl+Shift+V | | Ctrl+Shift+V | ctrl+shift+v | Ctrl+Shift+V | ctrl+shift+[KeyV] | | -| Alt+KeyV | v | Alt+V | | Alt+V | alt+v | Alt+V | alt+[KeyV] | | -| Ctrl+Alt+KeyV | √ | Ctrl+Alt+V | | Ctrl+Alt+V | ctrl+alt+v | Ctrl+Alt+V | ctrl+alt+[KeyV] | | -| Shift+Alt+KeyV | V | Shift+Alt+V | | Shift+Alt+V | shift+alt+v | Shift+Alt+V | shift+alt+[KeyV] | | -| Ctrl+Shift+Alt+KeyV | ◊ | Ctrl+Shift+Alt+V | | Ctrl+Shift+Alt+V | ctrl+shift+alt+v | Ctrl+Shift+Alt+V | ctrl+shift+alt+[KeyV] | | +| Alt+KeyV | v | Alt+V | | Option+V | alt+v | Alt+V | alt+[KeyV] | | +| Ctrl+Alt+KeyV | √ | Ctrl+Alt+V | | Ctrl+Option+V | ctrl+alt+v | Ctrl+Alt+V | ctrl+alt+[KeyV] | | +| Shift+Alt+KeyV | V | Shift+Alt+V | | Shift+Option+V | shift+alt+v | Shift+Alt+V | shift+alt+[KeyV] | | +| Ctrl+Shift+Alt+KeyV | ◊ | Ctrl+Shift+Alt+V | | Ctrl+Shift+Option+V | ctrl+shift+alt+v | Ctrl+Shift+Alt+V | ctrl+shift+alt+[KeyV] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyW | w | W | | W | w | W | [KeyW] | | | Ctrl+KeyW | w | Ctrl+W | | Ctrl+W | ctrl+w | Ctrl+W | ctrl+[KeyW] | | | Shift+KeyW | W | Shift+W | | Shift+W | shift+w | Shift+W | shift+[KeyW] | | | Ctrl+Shift+KeyW | W | Ctrl+Shift+W | | Ctrl+Shift+W | ctrl+shift+w | Ctrl+Shift+W | ctrl+shift+[KeyW] | | -| Alt+KeyW | w | Alt+W | | Alt+W | alt+w | Alt+W | alt+[KeyW] | | -| Ctrl+Alt+KeyW | ∑ | Ctrl+Alt+W | | Ctrl+Alt+W | ctrl+alt+w | Ctrl+Alt+W | ctrl+alt+[KeyW] | | -| Shift+Alt+KeyW | W | Shift+Alt+W | | Shift+Alt+W | shift+alt+w | Shift+Alt+W | shift+alt+[KeyW] | | -| Ctrl+Shift+Alt+KeyW | „ | Ctrl+Shift+Alt+W | | Ctrl+Shift+Alt+W | ctrl+shift+alt+w | Ctrl+Shift+Alt+W | ctrl+shift+alt+[KeyW] | | +| Alt+KeyW | w | Alt+W | | Option+W | alt+w | Alt+W | alt+[KeyW] | | +| Ctrl+Alt+KeyW | ∑ | Ctrl+Alt+W | | Ctrl+Option+W | ctrl+alt+w | Ctrl+Alt+W | ctrl+alt+[KeyW] | | +| Shift+Alt+KeyW | W | Shift+Alt+W | | Shift+Option+W | shift+alt+w | Shift+Alt+W | shift+alt+[KeyW] | | +| Ctrl+Shift+Alt+KeyW | „ | Ctrl+Shift+Alt+W | | Ctrl+Shift+Option+W | ctrl+shift+alt+w | Ctrl+Shift+Alt+W | ctrl+shift+alt+[KeyW] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyX | x | X | | X | x | X | [KeyX] | | | Ctrl+KeyX | x | Ctrl+X | | Ctrl+X | ctrl+x | Ctrl+X | ctrl+[KeyX] | | | Shift+KeyX | X | Shift+X | | Shift+X | shift+x | Shift+X | shift+[KeyX] | | | Ctrl+Shift+KeyX | X | Ctrl+Shift+X | | Ctrl+Shift+X | ctrl+shift+x | Ctrl+Shift+X | ctrl+shift+[KeyX] | | -| Alt+KeyX | x | Alt+X | | Alt+X | alt+x | Alt+X | alt+[KeyX] | | -| Ctrl+Alt+KeyX | ≈ | Ctrl+Alt+X | | Ctrl+Alt+X | ctrl+alt+x | Ctrl+Alt+X | ctrl+alt+[KeyX] | | -| Shift+Alt+KeyX | X | Shift+Alt+X | | Shift+Alt+X | shift+alt+x | Shift+Alt+X | shift+alt+[KeyX] | | -| Ctrl+Shift+Alt+KeyX | ˛ | Ctrl+Shift+Alt+X | | Ctrl+Shift+Alt+X | ctrl+shift+alt+x | Ctrl+Shift+Alt+X | ctrl+shift+alt+[KeyX] | | +| Alt+KeyX | x | Alt+X | | Option+X | alt+x | Alt+X | alt+[KeyX] | | +| Ctrl+Alt+KeyX | ≈ | Ctrl+Alt+X | | Ctrl+Option+X | ctrl+alt+x | Ctrl+Alt+X | ctrl+alt+[KeyX] | | +| Shift+Alt+KeyX | X | Shift+Alt+X | | Shift+Option+X | shift+alt+x | Shift+Alt+X | shift+alt+[KeyX] | | +| Ctrl+Shift+Alt+KeyX | ˛ | Ctrl+Shift+Alt+X | | Ctrl+Shift+Option+X | ctrl+shift+alt+x | Ctrl+Shift+Alt+X | ctrl+shift+alt+[KeyX] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -234,37 +234,37 @@ isUSStandard: true | Ctrl+KeyY | y | Ctrl+Y | | Ctrl+Y | ctrl+y | Ctrl+Y | ctrl+[KeyY] | | | Shift+KeyY | Y | Shift+Y | | Shift+Y | shift+y | Shift+Y | shift+[KeyY] | | | Ctrl+Shift+KeyY | Y | Ctrl+Shift+Y | | Ctrl+Shift+Y | ctrl+shift+y | Ctrl+Shift+Y | ctrl+shift+[KeyY] | | -| Alt+KeyY | y | Alt+Y | | Alt+Y | alt+y | Alt+Y | alt+[KeyY] | | -| Ctrl+Alt+KeyY | ¥ | Ctrl+Alt+Y | | Ctrl+Alt+Y | ctrl+alt+y | Ctrl+Alt+Y | ctrl+alt+[KeyY] | | -| Shift+Alt+KeyY | Y | Shift+Alt+Y | | Shift+Alt+Y | shift+alt+y | Shift+Alt+Y | shift+alt+[KeyY] | | -| Ctrl+Shift+Alt+KeyY | Á | Ctrl+Shift+Alt+Y | | Ctrl+Shift+Alt+Y | ctrl+shift+alt+y | Ctrl+Shift+Alt+Y | ctrl+shift+alt+[KeyY] | | +| Alt+KeyY | y | Alt+Y | | Option+Y | alt+y | Alt+Y | alt+[KeyY] | | +| Ctrl+Alt+KeyY | ¥ | Ctrl+Alt+Y | | Ctrl+Option+Y | ctrl+alt+y | Ctrl+Alt+Y | ctrl+alt+[KeyY] | | +| Shift+Alt+KeyY | Y | Shift+Alt+Y | | Shift+Option+Y | shift+alt+y | Shift+Alt+Y | shift+alt+[KeyY] | | +| Ctrl+Shift+Alt+KeyY | Á | Ctrl+Shift+Alt+Y | | Ctrl+Shift+Option+Y | ctrl+shift+alt+y | Ctrl+Shift+Alt+Y | ctrl+shift+alt+[KeyY] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyZ | z | Z | | Z | z | Z | [KeyZ] | | | Ctrl+KeyZ | z | Ctrl+Z | | Ctrl+Z | ctrl+z | Ctrl+Z | ctrl+[KeyZ] | | | Shift+KeyZ | Z | Shift+Z | | Shift+Z | shift+z | Shift+Z | shift+[KeyZ] | | | Ctrl+Shift+KeyZ | Z | Ctrl+Shift+Z | | Ctrl+Shift+Z | ctrl+shift+z | Ctrl+Shift+Z | ctrl+shift+[KeyZ] | | -| Alt+KeyZ | z | Alt+Z | | Alt+Z | alt+z | Alt+Z | alt+[KeyZ] | | -| Ctrl+Alt+KeyZ | Ω | Ctrl+Alt+Z | | Ctrl+Alt+Z | ctrl+alt+z | Ctrl+Alt+Z | ctrl+alt+[KeyZ] | | -| Shift+Alt+KeyZ | Z | Shift+Alt+Z | | Shift+Alt+Z | shift+alt+z | Shift+Alt+Z | shift+alt+[KeyZ] | | -| Ctrl+Shift+Alt+KeyZ | ¸ | Ctrl+Shift+Alt+Z | | Ctrl+Shift+Alt+Z | ctrl+shift+alt+z | Ctrl+Shift+Alt+Z | ctrl+shift+alt+[KeyZ] | | +| Alt+KeyZ | z | Alt+Z | | Option+Z | alt+z | Alt+Z | alt+[KeyZ] | | +| Ctrl+Alt+KeyZ | Ω | Ctrl+Alt+Z | | Ctrl+Option+Z | ctrl+alt+z | Ctrl+Alt+Z | ctrl+alt+[KeyZ] | | +| Shift+Alt+KeyZ | Z | Shift+Alt+Z | | Shift+Option+Z | shift+alt+z | Shift+Alt+Z | shift+alt+[KeyZ] | | +| Ctrl+Shift+Alt+KeyZ | ¸ | Ctrl+Shift+Alt+Z | | Ctrl+Shift+Option+Z | ctrl+shift+alt+z | Ctrl+Shift+Alt+Z | ctrl+shift+alt+[KeyZ] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit1 | 1 | 1 | | 1 | 1 | 1 | [Digit1] | | | Ctrl+Digit1 | 1 | Ctrl+1 | | Ctrl+1 | ctrl+1 | Ctrl+1 | ctrl+[Digit1] | | | Shift+Digit1 | ! | Shift+1 | | Shift+1 | shift+1 | Shift+1 | shift+[Digit1] | | | Ctrl+Shift+Digit1 | ! | Ctrl+Shift+1 | | Ctrl+Shift+1 | ctrl+shift+1 | Ctrl+Shift+1 | ctrl+shift+[Digit1] | | -| Alt+Digit1 | 1 | Alt+1 | | Alt+1 | alt+1 | Alt+1 | alt+[Digit1] | | -| Ctrl+Alt+Digit1 | ¡ | Ctrl+Alt+1 | | Ctrl+Alt+1 | ctrl+alt+1 | Ctrl+Alt+1 | ctrl+alt+[Digit1] | | -| Shift+Alt+Digit1 | ! | Shift+Alt+1 | | Shift+Alt+1 | shift+alt+1 | Shift+Alt+1 | shift+alt+[Digit1] | | -| Ctrl+Shift+Alt+Digit1 | ⁄ | Ctrl+Shift+Alt+1 | | Ctrl+Shift+Alt+1 | ctrl+shift+alt+1 | Ctrl+Shift+Alt+1 | ctrl+shift+alt+[Digit1] | | +| Alt+Digit1 | 1 | Alt+1 | | Option+1 | alt+1 | Alt+1 | alt+[Digit1] | | +| Ctrl+Alt+Digit1 | ¡ | Ctrl+Alt+1 | | Ctrl+Option+1 | ctrl+alt+1 | Ctrl+Alt+1 | ctrl+alt+[Digit1] | | +| Shift+Alt+Digit1 | ! | Shift+Alt+1 | | Shift+Option+1 | shift+alt+1 | Shift+Alt+1 | shift+alt+[Digit1] | | +| Ctrl+Shift+Alt+Digit1 | ⁄ | Ctrl+Shift+Alt+1 | | Ctrl+Shift+Option+1 | ctrl+shift+alt+1 | Ctrl+Shift+Alt+1 | ctrl+shift+alt+[Digit1] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit2 | 2 | 2 | | 2 | 2 | 2 | [Digit2] | | | Ctrl+Digit2 | 2 | Ctrl+2 | | Ctrl+2 | ctrl+2 | Ctrl+2 | ctrl+[Digit2] | | | Shift+Digit2 | @ | Shift+2 | | Shift+2 | shift+2 | Shift+2 | shift+[Digit2] | | | Ctrl+Shift+Digit2 | @ | Ctrl+Shift+2 | | Ctrl+Shift+2 | ctrl+shift+2 | Ctrl+Shift+2 | ctrl+shift+[Digit2] | | -| Alt+Digit2 | 2 | Alt+2 | | Alt+2 | alt+2 | Alt+2 | alt+[Digit2] | | -| Ctrl+Alt+Digit2 | ™ | Ctrl+Alt+2 | | Ctrl+Alt+2 | ctrl+alt+2 | Ctrl+Alt+2 | ctrl+alt+[Digit2] | | -| Shift+Alt+Digit2 | @ | Shift+Alt+2 | | Shift+Alt+2 | shift+alt+2 | Shift+Alt+2 | shift+alt+[Digit2] | | -| Ctrl+Shift+Alt+Digit2 | € | Ctrl+Shift+Alt+2 | | Ctrl+Shift+Alt+2 | ctrl+shift+alt+2 | Ctrl+Shift+Alt+2 | ctrl+shift+alt+[Digit2] | | +| Alt+Digit2 | 2 | Alt+2 | | Option+2 | alt+2 | Alt+2 | alt+[Digit2] | | +| Ctrl+Alt+Digit2 | ™ | Ctrl+Alt+2 | | Ctrl+Option+2 | ctrl+alt+2 | Ctrl+Alt+2 | ctrl+alt+[Digit2] | | +| Shift+Alt+Digit2 | @ | Shift+Alt+2 | | Shift+Option+2 | shift+alt+2 | Shift+Alt+2 | shift+alt+[Digit2] | | +| Ctrl+Shift+Alt+Digit2 | € | Ctrl+Shift+Alt+2 | | Ctrl+Shift+Option+2 | ctrl+shift+alt+2 | Ctrl+Shift+Alt+2 | ctrl+shift+alt+[Digit2] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -272,37 +272,37 @@ isUSStandard: true | Ctrl+Digit3 | 3 | Ctrl+3 | | Ctrl+3 | ctrl+3 | Ctrl+3 | ctrl+[Digit3] | | | Shift+Digit3 | # | Shift+3 | | Shift+3 | shift+3 | Shift+3 | shift+[Digit3] | | | Ctrl+Shift+Digit3 | # | Ctrl+Shift+3 | | Ctrl+Shift+3 | ctrl+shift+3 | Ctrl+Shift+3 | ctrl+shift+[Digit3] | | -| Alt+Digit3 | 3 | Alt+3 | | Alt+3 | alt+3 | Alt+3 | alt+[Digit3] | | -| Ctrl+Alt+Digit3 | £ | Ctrl+Alt+3 | | Ctrl+Alt+3 | ctrl+alt+3 | Ctrl+Alt+3 | ctrl+alt+[Digit3] | | -| Shift+Alt+Digit3 | # | Shift+Alt+3 | | Shift+Alt+3 | shift+alt+3 | Shift+Alt+3 | shift+alt+[Digit3] | | -| Ctrl+Shift+Alt+Digit3 | ‹ | Ctrl+Shift+Alt+3 | | Ctrl+Shift+Alt+3 | ctrl+shift+alt+3 | Ctrl+Shift+Alt+3 | ctrl+shift+alt+[Digit3] | | +| Alt+Digit3 | 3 | Alt+3 | | Option+3 | alt+3 | Alt+3 | alt+[Digit3] | | +| Ctrl+Alt+Digit3 | £ | Ctrl+Alt+3 | | Ctrl+Option+3 | ctrl+alt+3 | Ctrl+Alt+3 | ctrl+alt+[Digit3] | | +| Shift+Alt+Digit3 | # | Shift+Alt+3 | | Shift+Option+3 | shift+alt+3 | Shift+Alt+3 | shift+alt+[Digit3] | | +| Ctrl+Shift+Alt+Digit3 | ‹ | Ctrl+Shift+Alt+3 | | Ctrl+Shift+Option+3 | ctrl+shift+alt+3 | Ctrl+Shift+Alt+3 | ctrl+shift+alt+[Digit3] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit4 | 4 | 4 | | 4 | 4 | 4 | [Digit4] | | | Ctrl+Digit4 | 4 | Ctrl+4 | | Ctrl+4 | ctrl+4 | Ctrl+4 | ctrl+[Digit4] | | | Shift+Digit4 | $ | Shift+4 | | Shift+4 | shift+4 | Shift+4 | shift+[Digit4] | | | Ctrl+Shift+Digit4 | $ | Ctrl+Shift+4 | | Ctrl+Shift+4 | ctrl+shift+4 | Ctrl+Shift+4 | ctrl+shift+[Digit4] | | -| Alt+Digit4 | 4 | Alt+4 | | Alt+4 | alt+4 | Alt+4 | alt+[Digit4] | | -| Ctrl+Alt+Digit4 | ¢ | Ctrl+Alt+4 | | Ctrl+Alt+4 | ctrl+alt+4 | Ctrl+Alt+4 | ctrl+alt+[Digit4] | | -| Shift+Alt+Digit4 | $ | Shift+Alt+4 | | Shift+Alt+4 | shift+alt+4 | Shift+Alt+4 | shift+alt+[Digit4] | | -| Ctrl+Shift+Alt+Digit4 | › | Ctrl+Shift+Alt+4 | | Ctrl+Shift+Alt+4 | ctrl+shift+alt+4 | Ctrl+Shift+Alt+4 | ctrl+shift+alt+[Digit4] | | +| Alt+Digit4 | 4 | Alt+4 | | Option+4 | alt+4 | Alt+4 | alt+[Digit4] | | +| Ctrl+Alt+Digit4 | ¢ | Ctrl+Alt+4 | | Ctrl+Option+4 | ctrl+alt+4 | Ctrl+Alt+4 | ctrl+alt+[Digit4] | | +| Shift+Alt+Digit4 | $ | Shift+Alt+4 | | Shift+Option+4 | shift+alt+4 | Shift+Alt+4 | shift+alt+[Digit4] | | +| Ctrl+Shift+Alt+Digit4 | › | Ctrl+Shift+Alt+4 | | Ctrl+Shift+Option+4 | ctrl+shift+alt+4 | Ctrl+Shift+Alt+4 | ctrl+shift+alt+[Digit4] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit5 | 5 | 5 | | 5 | 5 | 5 | [Digit5] | | | Ctrl+Digit5 | 5 | Ctrl+5 | | Ctrl+5 | ctrl+5 | Ctrl+5 | ctrl+[Digit5] | | | Shift+Digit5 | % | Shift+5 | | Shift+5 | shift+5 | Shift+5 | shift+[Digit5] | | | Ctrl+Shift+Digit5 | % | Ctrl+Shift+5 | | Ctrl+Shift+5 | ctrl+shift+5 | Ctrl+Shift+5 | ctrl+shift+[Digit5] | | -| Alt+Digit5 | 5 | Alt+5 | | Alt+5 | alt+5 | Alt+5 | alt+[Digit5] | | -| Ctrl+Alt+Digit5 | ∞ | Ctrl+Alt+5 | | Ctrl+Alt+5 | ctrl+alt+5 | Ctrl+Alt+5 | ctrl+alt+[Digit5] | | -| Shift+Alt+Digit5 | % | Shift+Alt+5 | | Shift+Alt+5 | shift+alt+5 | Shift+Alt+5 | shift+alt+[Digit5] | | -| Ctrl+Shift+Alt+Digit5 | fi | Ctrl+Shift+Alt+5 | | Ctrl+Shift+Alt+5 | ctrl+shift+alt+5 | Ctrl+Shift+Alt+5 | ctrl+shift+alt+[Digit5] | | +| Alt+Digit5 | 5 | Alt+5 | | Option+5 | alt+5 | Alt+5 | alt+[Digit5] | | +| Ctrl+Alt+Digit5 | ∞ | Ctrl+Alt+5 | | Ctrl+Option+5 | ctrl+alt+5 | Ctrl+Alt+5 | ctrl+alt+[Digit5] | | +| Shift+Alt+Digit5 | % | Shift+Alt+5 | | Shift+Option+5 | shift+alt+5 | Shift+Alt+5 | shift+alt+[Digit5] | | +| Ctrl+Shift+Alt+Digit5 | fi | Ctrl+Shift+Alt+5 | | Ctrl+Shift+Option+5 | ctrl+shift+alt+5 | Ctrl+Shift+Alt+5 | ctrl+shift+alt+[Digit5] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit6 | 6 | 6 | | 6 | 6 | 6 | [Digit6] | | | Ctrl+Digit6 | 6 | Ctrl+6 | | Ctrl+6 | ctrl+6 | Ctrl+6 | ctrl+[Digit6] | | | Shift+Digit6 | ^ | Shift+6 | | Shift+6 | shift+6 | Shift+6 | shift+[Digit6] | | | Ctrl+Shift+Digit6 | ^ | Ctrl+Shift+6 | | Ctrl+Shift+6 | ctrl+shift+6 | Ctrl+Shift+6 | ctrl+shift+[Digit6] | | -| Alt+Digit6 | 6 | Alt+6 | | Alt+6 | alt+6 | Alt+6 | alt+[Digit6] | | -| Ctrl+Alt+Digit6 | § | Ctrl+Alt+6 | | Ctrl+Alt+6 | ctrl+alt+6 | Ctrl+Alt+6 | ctrl+alt+[Digit6] | | -| Shift+Alt+Digit6 | ^ | Shift+Alt+6 | | Shift+Alt+6 | shift+alt+6 | Shift+Alt+6 | shift+alt+[Digit6] | | -| Ctrl+Shift+Alt+Digit6 | fl | Ctrl+Shift+Alt+6 | | Ctrl+Shift+Alt+6 | ctrl+shift+alt+6 | Ctrl+Shift+Alt+6 | ctrl+shift+alt+[Digit6] | | +| Alt+Digit6 | 6 | Alt+6 | | Option+6 | alt+6 | Alt+6 | alt+[Digit6] | | +| Ctrl+Alt+Digit6 | § | Ctrl+Alt+6 | | Ctrl+Option+6 | ctrl+alt+6 | Ctrl+Alt+6 | ctrl+alt+[Digit6] | | +| Shift+Alt+Digit6 | ^ | Shift+Alt+6 | | Shift+Option+6 | shift+alt+6 | Shift+Alt+6 | shift+alt+[Digit6] | | +| Ctrl+Shift+Alt+Digit6 | fl | Ctrl+Shift+Alt+6 | | Ctrl+Shift+Option+6 | ctrl+shift+alt+6 | Ctrl+Shift+Alt+6 | ctrl+shift+alt+[Digit6] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -310,37 +310,37 @@ isUSStandard: true | Ctrl+Digit7 | 7 | Ctrl+7 | | Ctrl+7 | ctrl+7 | Ctrl+7 | ctrl+[Digit7] | | | Shift+Digit7 | & | Shift+7 | | Shift+7 | shift+7 | Shift+7 | shift+[Digit7] | | | Ctrl+Shift+Digit7 | & | Ctrl+Shift+7 | | Ctrl+Shift+7 | ctrl+shift+7 | Ctrl+Shift+7 | ctrl+shift+[Digit7] | | -| Alt+Digit7 | 7 | Alt+7 | | Alt+7 | alt+7 | Alt+7 | alt+[Digit7] | | -| Ctrl+Alt+Digit7 | ¶ | Ctrl+Alt+7 | | Ctrl+Alt+7 | ctrl+alt+7 | Ctrl+Alt+7 | ctrl+alt+[Digit7] | | -| Shift+Alt+Digit7 | & | Shift+Alt+7 | | Shift+Alt+7 | shift+alt+7 | Shift+Alt+7 | shift+alt+[Digit7] | | -| Ctrl+Shift+Alt+Digit7 | ‡ | Ctrl+Shift+Alt+7 | | Ctrl+Shift+Alt+7 | ctrl+shift+alt+7 | Ctrl+Shift+Alt+7 | ctrl+shift+alt+[Digit7] | | +| Alt+Digit7 | 7 | Alt+7 | | Option+7 | alt+7 | Alt+7 | alt+[Digit7] | | +| Ctrl+Alt+Digit7 | ¶ | Ctrl+Alt+7 | | Ctrl+Option+7 | ctrl+alt+7 | Ctrl+Alt+7 | ctrl+alt+[Digit7] | | +| Shift+Alt+Digit7 | & | Shift+Alt+7 | | Shift+Option+7 | shift+alt+7 | Shift+Alt+7 | shift+alt+[Digit7] | | +| Ctrl+Shift+Alt+Digit7 | ‡ | Ctrl+Shift+Alt+7 | | Ctrl+Shift+Option+7 | ctrl+shift+alt+7 | Ctrl+Shift+Alt+7 | ctrl+shift+alt+[Digit7] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit8 | 8 | 8 | | 8 | 8 | 8 | [Digit8] | | | Ctrl+Digit8 | 8 | Ctrl+8 | | Ctrl+8 | ctrl+8 | Ctrl+8 | ctrl+[Digit8] | | | Shift+Digit8 | * | Shift+8 | | Shift+8 | shift+8 | Shift+8 | shift+[Digit8] | | | Ctrl+Shift+Digit8 | * | Ctrl+Shift+8 | | Ctrl+Shift+8 | ctrl+shift+8 | Ctrl+Shift+8 | ctrl+shift+[Digit8] | | -| Alt+Digit8 | 8 | Alt+8 | | Alt+8 | alt+8 | Alt+8 | alt+[Digit8] | | -| Ctrl+Alt+Digit8 | • | Ctrl+Alt+8 | | Ctrl+Alt+8 | ctrl+alt+8 | Ctrl+Alt+8 | ctrl+alt+[Digit8] | | -| Shift+Alt+Digit8 | * | Shift+Alt+8 | | Shift+Alt+8 | shift+alt+8 | Shift+Alt+8 | shift+alt+[Digit8] | | -| Ctrl+Shift+Alt+Digit8 | ° | Ctrl+Shift+Alt+8 | | Ctrl+Shift+Alt+8 | ctrl+shift+alt+8 | Ctrl+Shift+Alt+8 | ctrl+shift+alt+[Digit8] | | +| Alt+Digit8 | 8 | Alt+8 | | Option+8 | alt+8 | Alt+8 | alt+[Digit8] | | +| Ctrl+Alt+Digit8 | • | Ctrl+Alt+8 | | Ctrl+Option+8 | ctrl+alt+8 | Ctrl+Alt+8 | ctrl+alt+[Digit8] | | +| Shift+Alt+Digit8 | * | Shift+Alt+8 | | Shift+Option+8 | shift+alt+8 | Shift+Alt+8 | shift+alt+[Digit8] | | +| Ctrl+Shift+Alt+Digit8 | ° | Ctrl+Shift+Alt+8 | | Ctrl+Shift+Option+8 | ctrl+shift+alt+8 | Ctrl+Shift+Alt+8 | ctrl+shift+alt+[Digit8] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit9 | 9 | 9 | | 9 | 9 | 9 | [Digit9] | | | Ctrl+Digit9 | 9 | Ctrl+9 | | Ctrl+9 | ctrl+9 | Ctrl+9 | ctrl+[Digit9] | | | Shift+Digit9 | ( | Shift+9 | | Shift+9 | shift+9 | Shift+9 | shift+[Digit9] | | | Ctrl+Shift+Digit9 | ( | Ctrl+Shift+9 | | Ctrl+Shift+9 | ctrl+shift+9 | Ctrl+Shift+9 | ctrl+shift+[Digit9] | | -| Alt+Digit9 | 9 | Alt+9 | | Alt+9 | alt+9 | Alt+9 | alt+[Digit9] | | -| Ctrl+Alt+Digit9 | ª | Ctrl+Alt+9 | | Ctrl+Alt+9 | ctrl+alt+9 | Ctrl+Alt+9 | ctrl+alt+[Digit9] | | -| Shift+Alt+Digit9 | ( | Shift+Alt+9 | | Shift+Alt+9 | shift+alt+9 | Shift+Alt+9 | shift+alt+[Digit9] | | -| Ctrl+Shift+Alt+Digit9 | · | Ctrl+Shift+Alt+9 | | Ctrl+Shift+Alt+9 | ctrl+shift+alt+9 | Ctrl+Shift+Alt+9 | ctrl+shift+alt+[Digit9] | | +| Alt+Digit9 | 9 | Alt+9 | | Option+9 | alt+9 | Alt+9 | alt+[Digit9] | | +| Ctrl+Alt+Digit9 | ª | Ctrl+Alt+9 | | Ctrl+Option+9 | ctrl+alt+9 | Ctrl+Alt+9 | ctrl+alt+[Digit9] | | +| Shift+Alt+Digit9 | ( | Shift+Alt+9 | | Shift+Option+9 | shift+alt+9 | Shift+Alt+9 | shift+alt+[Digit9] | | +| Ctrl+Shift+Alt+Digit9 | · | Ctrl+Shift+Alt+9 | | Ctrl+Shift+Option+9 | ctrl+shift+alt+9 | Ctrl+Shift+Alt+9 | ctrl+shift+alt+[Digit9] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit0 | 0 | 0 | | 0 | 0 | 0 | [Digit0] | | | Ctrl+Digit0 | 0 | Ctrl+0 | | Ctrl+0 | ctrl+0 | Ctrl+0 | ctrl+[Digit0] | | | Shift+Digit0 | ) | Shift+0 | | Shift+0 | shift+0 | Shift+0 | shift+[Digit0] | | | Ctrl+Shift+Digit0 | ) | Ctrl+Shift+0 | | Ctrl+Shift+0 | ctrl+shift+0 | Ctrl+Shift+0 | ctrl+shift+[Digit0] | | -| Alt+Digit0 | 0 | Alt+0 | | Alt+0 | alt+0 | Alt+0 | alt+[Digit0] | | -| Ctrl+Alt+Digit0 | º | Ctrl+Alt+0 | | Ctrl+Alt+0 | ctrl+alt+0 | Ctrl+Alt+0 | ctrl+alt+[Digit0] | | -| Shift+Alt+Digit0 | ) | Shift+Alt+0 | | Shift+Alt+0 | shift+alt+0 | Shift+Alt+0 | shift+alt+[Digit0] | | -| Ctrl+Shift+Alt+Digit0 | ‚ | Ctrl+Shift+Alt+0 | | Ctrl+Shift+Alt+0 | ctrl+shift+alt+0 | Ctrl+Shift+Alt+0 | ctrl+shift+alt+[Digit0] | | +| Alt+Digit0 | 0 | Alt+0 | | Option+0 | alt+0 | Alt+0 | alt+[Digit0] | | +| Ctrl+Alt+Digit0 | º | Ctrl+Alt+0 | | Ctrl+Option+0 | ctrl+alt+0 | Ctrl+Alt+0 | ctrl+alt+[Digit0] | | +| Shift+Alt+Digit0 | ) | Shift+Alt+0 | | Shift+Option+0 | shift+alt+0 | Shift+Alt+0 | shift+alt+[Digit0] | | +| Ctrl+Shift+Alt+Digit0 | ‚ | Ctrl+Shift+Alt+0 | | Ctrl+Shift+Option+0 | ctrl+shift+alt+0 | Ctrl+Shift+Alt+0 | ctrl+shift+alt+[Digit0] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -348,37 +348,37 @@ isUSStandard: true | Ctrl+Minus | - | Ctrl+- | | Ctrl+- | ctrl+- | Ctrl+- | ctrl+[Minus] | | | Shift+Minus | _ | Shift+- | | Shift+- | shift+- | Shift+- | shift+[Minus] | | | Ctrl+Shift+Minus | _ | Ctrl+Shift+- | | Ctrl+Shift+- | ctrl+shift+- | Ctrl+Shift+- | ctrl+shift+[Minus] | | -| Alt+Minus | - | Alt+- | | Alt+- | alt+- | Alt+- | alt+[Minus] | | -| Ctrl+Alt+Minus | – | Ctrl+Alt+- | | Ctrl+Alt+- | ctrl+alt+- | Ctrl+Alt+- | ctrl+alt+[Minus] | | -| Shift+Alt+Minus | _ | Shift+Alt+- | | Shift+Alt+- | shift+alt+- | Shift+Alt+- | shift+alt+[Minus] | | -| Ctrl+Shift+Alt+Minus | — | Ctrl+Shift+Alt+- | | Ctrl+Shift+Alt+- | ctrl+shift+alt+- | Ctrl+Shift+Alt+- | ctrl+shift+alt+[Minus] | | +| Alt+Minus | - | Alt+- | | Option+- | alt+- | Alt+- | alt+[Minus] | | +| Ctrl+Alt+Minus | – | Ctrl+Alt+- | | Ctrl+Option+- | ctrl+alt+- | Ctrl+Alt+- | ctrl+alt+[Minus] | | +| Shift+Alt+Minus | _ | Shift+Alt+- | | Shift+Option+- | shift+alt+- | Shift+Alt+- | shift+alt+[Minus] | | +| Ctrl+Shift+Alt+Minus | — | Ctrl+Shift+Alt+- | | Ctrl+Shift+Option+- | ctrl+shift+alt+- | Ctrl+Shift+Alt+- | ctrl+shift+alt+[Minus] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Equal | = | = | | = | = | = | [Equal] | | | Ctrl+Equal | = | Ctrl+= | | Ctrl+= | ctrl+= | Ctrl+= | ctrl+[Equal] | | | Shift+Equal | + | Shift+= | | Shift+= | shift+= | Shift+= | shift+[Equal] | | | Ctrl+Shift+Equal | + | Ctrl+Shift+= | | Ctrl+Shift+= | ctrl+shift+= | Ctrl+Shift+= | ctrl+shift+[Equal] | | -| Alt+Equal | = | Alt+= | | Alt+= | alt+= | Alt+= | alt+[Equal] | | -| Ctrl+Alt+Equal | ≠ | Ctrl+Alt+= | | Ctrl+Alt+= | ctrl+alt+= | Ctrl+Alt+= | ctrl+alt+[Equal] | | -| Shift+Alt+Equal | + | Shift+Alt+= | | Shift+Alt+= | shift+alt+= | Shift+Alt+= | shift+alt+[Equal] | | -| Ctrl+Shift+Alt+Equal | ± | Ctrl+Shift+Alt+= | | Ctrl+Shift+Alt+= | ctrl+shift+alt+= | Ctrl+Shift+Alt+= | ctrl+shift+alt+[Equal] | | +| Alt+Equal | = | Alt+= | | Option+= | alt+= | Alt+= | alt+[Equal] | | +| Ctrl+Alt+Equal | ≠ | Ctrl+Alt+= | | Ctrl+Option+= | ctrl+alt+= | Ctrl+Alt+= | ctrl+alt+[Equal] | | +| Shift+Alt+Equal | + | Shift+Alt+= | | Shift+Option+= | shift+alt+= | Shift+Alt+= | shift+alt+[Equal] | | +| Ctrl+Shift+Alt+Equal | ± | Ctrl+Shift+Alt+= | | Ctrl+Shift+Option+= | ctrl+shift+alt+= | Ctrl+Shift+Alt+= | ctrl+shift+alt+[Equal] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | BracketLeft | [ | [ | | [ | [ | [ | [BracketLeft] | | | Ctrl+BracketLeft | [ | Ctrl+[ | | Ctrl+[ | ctrl+[ | Ctrl+[ | ctrl+[BracketLeft] | | | Shift+BracketLeft | { | Shift+[ | | Shift+[ | shift+[ | Shift+[ | shift+[BracketLeft] | | | Ctrl+Shift+BracketLeft | { | Ctrl+Shift+[ | | Ctrl+Shift+[ | ctrl+shift+[ | Ctrl+Shift+[ | ctrl+shift+[BracketLeft] | | -| Alt+BracketLeft | [ | Alt+[ | | Alt+[ | alt+[ | Alt+[ | alt+[BracketLeft] | | -| Ctrl+Alt+BracketLeft | “ | Ctrl+Alt+[ | | Ctrl+Alt+[ | ctrl+alt+[ | Ctrl+Alt+[ | ctrl+alt+[BracketLeft] | | -| Shift+Alt+BracketLeft | { | Shift+Alt+[ | | Shift+Alt+[ | shift+alt+[ | Shift+Alt+[ | shift+alt+[BracketLeft] | | -| Ctrl+Shift+Alt+BracketLeft | ” | Ctrl+Shift+Alt+[ | | Ctrl+Shift+Alt+[ | ctrl+shift+alt+[ | Ctrl+Shift+Alt+[ | ctrl+shift+alt+[BracketLeft] | | +| Alt+BracketLeft | [ | Alt+[ | | Option+[ | alt+[ | Alt+[ | alt+[BracketLeft] | | +| Ctrl+Alt+BracketLeft | “ | Ctrl+Alt+[ | | Ctrl+Option+[ | ctrl+alt+[ | Ctrl+Alt+[ | ctrl+alt+[BracketLeft] | | +| Shift+Alt+BracketLeft | { | Shift+Alt+[ | | Shift+Option+[ | shift+alt+[ | Shift+Alt+[ | shift+alt+[BracketLeft] | | +| Ctrl+Shift+Alt+BracketLeft | ” | Ctrl+Shift+Alt+[ | | Ctrl+Shift+Option+[ | ctrl+shift+alt+[ | Ctrl+Shift+Alt+[ | ctrl+shift+alt+[BracketLeft] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | BracketRight | ] | ] | | ] | ] | ] | [BracketRight] | | | Ctrl+BracketRight | ] | Ctrl+] | | Ctrl+] | ctrl+] | Ctrl+] | ctrl+[BracketRight] | | | Shift+BracketRight | } | Shift+] | | Shift+] | shift+] | Shift+] | shift+[BracketRight] | | | Ctrl+Shift+BracketRight | } | Ctrl+Shift+] | | Ctrl+Shift+] | ctrl+shift+] | Ctrl+Shift+] | ctrl+shift+[BracketRight] | | -| Alt+BracketRight | ] | Alt+] | | Alt+] | alt+] | Alt+] | alt+[BracketRight] | | -| Ctrl+Alt+BracketRight | ‘ | Ctrl+Alt+] | | Ctrl+Alt+] | ctrl+alt+] | Ctrl+Alt+] | ctrl+alt+[BracketRight] | | -| Shift+Alt+BracketRight | } | Shift+Alt+] | | Shift+Alt+] | shift+alt+] | Shift+Alt+] | shift+alt+[BracketRight] | | -| Ctrl+Shift+Alt+BracketRight | ’ | Ctrl+Shift+Alt+] | | Ctrl+Shift+Alt+] | ctrl+shift+alt+] | Ctrl+Shift+Alt+] | ctrl+shift+alt+[BracketRight] | | +| Alt+BracketRight | ] | Alt+] | | Option+] | alt+] | Alt+] | alt+[BracketRight] | | +| Ctrl+Alt+BracketRight | ‘ | Ctrl+Alt+] | | Ctrl+Option+] | ctrl+alt+] | Ctrl+Alt+] | ctrl+alt+[BracketRight] | | +| Shift+Alt+BracketRight | } | Shift+Alt+] | | Shift+Option+] | shift+alt+] | Shift+Alt+] | shift+alt+[BracketRight] | | +| Ctrl+Shift+Alt+BracketRight | ’ | Ctrl+Shift+Alt+] | | Ctrl+Shift+Option+] | ctrl+shift+alt+] | Ctrl+Shift+Alt+] | ctrl+shift+alt+[BracketRight] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -386,10 +386,10 @@ isUSStandard: true | Ctrl+Backslash | \ | Ctrl+\ | | Ctrl+\ | ctrl+\ | Ctrl+\ | ctrl+[Backslash] | | | Shift+Backslash | | | Shift+\ | | Shift+\ | shift+\ | Shift+\ | shift+[Backslash] | | | Ctrl+Shift+Backslash | | | Ctrl+Shift+\ | | Ctrl+Shift+\ | ctrl+shift+\ | Ctrl+Shift+\ | ctrl+shift+[Backslash] | | -| Alt+Backslash | \ | Alt+\ | | Alt+\ | alt+\ | Alt+\ | alt+[Backslash] | | -| Ctrl+Alt+Backslash | « | Ctrl+Alt+\ | | Ctrl+Alt+\ | ctrl+alt+\ | Ctrl+Alt+\ | ctrl+alt+[Backslash] | | -| Shift+Alt+Backslash | | | Shift+Alt+\ | | Shift+Alt+\ | shift+alt+\ | Shift+Alt+\ | shift+alt+[Backslash] | | -| Ctrl+Shift+Alt+Backslash | » | Ctrl+Shift+Alt+\ | | Ctrl+Shift+Alt+\ | ctrl+shift+alt+\ | Ctrl+Shift+Alt+\ | ctrl+shift+alt+[Backslash] | | +| Alt+Backslash | \ | Alt+\ | | Option+\ | alt+\ | Alt+\ | alt+[Backslash] | | +| Ctrl+Alt+Backslash | « | Ctrl+Alt+\ | | Ctrl+Option+\ | ctrl+alt+\ | Ctrl+Alt+\ | ctrl+alt+[Backslash] | | +| Shift+Alt+Backslash | | | Shift+Alt+\ | | Shift+Option+\ | shift+alt+\ | Shift+Alt+\ | shift+alt+[Backslash] | | +| Ctrl+Shift+Alt+Backslash | » | Ctrl+Shift+Alt+\ | | Ctrl+Shift+Option+\ | ctrl+shift+alt+\ | Ctrl+Shift+Alt+\ | ctrl+shift+alt+[Backslash] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | IntlHash | --- | | | null | null | null | null | | | Ctrl+IntlHash | --- | | | null | null | null | null | | @@ -404,19 +404,19 @@ isUSStandard: true | Ctrl+Semicolon | ; | Ctrl+; | | Ctrl+; | ctrl+; | Ctrl+; | ctrl+[Semicolon] | | | Shift+Semicolon | : | Shift+; | | Shift+; | shift+; | Shift+; | shift+[Semicolon] | | | Ctrl+Shift+Semicolon | : | Ctrl+Shift+; | | Ctrl+Shift+; | ctrl+shift+; | Ctrl+Shift+; | ctrl+shift+[Semicolon] | | -| Alt+Semicolon | ; | Alt+; | | Alt+; | alt+; | Alt+; | alt+[Semicolon] | | -| Ctrl+Alt+Semicolon | … | Ctrl+Alt+; | | Ctrl+Alt+; | ctrl+alt+; | Ctrl+Alt+; | ctrl+alt+[Semicolon] | | -| Shift+Alt+Semicolon | : | Shift+Alt+; | | Shift+Alt+; | shift+alt+; | Shift+Alt+; | shift+alt+[Semicolon] | | -| Ctrl+Shift+Alt+Semicolon | Ú | Ctrl+Shift+Alt+; | | Ctrl+Shift+Alt+; | ctrl+shift+alt+; | Ctrl+Shift+Alt+; | ctrl+shift+alt+[Semicolon] | | +| Alt+Semicolon | ; | Alt+; | | Option+; | alt+; | Alt+; | alt+[Semicolon] | | +| Ctrl+Alt+Semicolon | … | Ctrl+Alt+; | | Ctrl+Option+; | ctrl+alt+; | Ctrl+Alt+; | ctrl+alt+[Semicolon] | | +| Shift+Alt+Semicolon | : | Shift+Alt+; | | Shift+Option+; | shift+alt+; | Shift+Alt+; | shift+alt+[Semicolon] | | +| Ctrl+Shift+Alt+Semicolon | Ú | Ctrl+Shift+Alt+; | | Ctrl+Shift+Option+; | ctrl+shift+alt+; | Ctrl+Shift+Alt+; | ctrl+shift+alt+[Semicolon] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Quote | ' | ' | | ' | ' | ' | [Quote] | | | Ctrl+Quote | ' | Ctrl+' | | Ctrl+' | ctrl+' | Ctrl+' | ctrl+[Quote] | | | Shift+Quote | " | Shift+' | | Shift+' | shift+' | Shift+' | shift+[Quote] | | | Ctrl+Shift+Quote | " | Ctrl+Shift+' | | Ctrl+Shift+' | ctrl+shift+' | Ctrl+Shift+' | ctrl+shift+[Quote] | | -| Alt+Quote | ' | Alt+' | | Alt+' | alt+' | Alt+' | alt+[Quote] | | -| Ctrl+Alt+Quote | æ | Ctrl+Alt+' | | Ctrl+Alt+' | ctrl+alt+' | Ctrl+Alt+' | ctrl+alt+[Quote] | | -| Shift+Alt+Quote | " | Shift+Alt+' | | Shift+Alt+' | shift+alt+' | Shift+Alt+' | shift+alt+[Quote] | | -| Ctrl+Shift+Alt+Quote | Æ | Ctrl+Shift+Alt+' | | Ctrl+Shift+Alt+' | ctrl+shift+alt+' | Ctrl+Shift+Alt+' | ctrl+shift+alt+[Quote] | | +| Alt+Quote | ' | Alt+' | | Option+' | alt+' | Alt+' | alt+[Quote] | | +| Ctrl+Alt+Quote | æ | Ctrl+Alt+' | | Ctrl+Option+' | ctrl+alt+' | Ctrl+Alt+' | ctrl+alt+[Quote] | | +| Shift+Alt+Quote | " | Shift+Alt+' | | Shift+Option+' | shift+alt+' | Shift+Alt+' | shift+alt+[Quote] | | +| Ctrl+Shift+Alt+Quote | Æ | Ctrl+Shift+Alt+' | | Ctrl+Shift+Option+' | ctrl+shift+alt+' | Ctrl+Shift+Alt+' | ctrl+shift+alt+[Quote] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -424,37 +424,37 @@ isUSStandard: true | Ctrl+Backquote | ` | Ctrl+` | | Ctrl+` | ctrl+` | Ctrl+` | ctrl+[Backquote] | | | Shift+Backquote | ~ | Shift+` | | Shift+` | shift+` | Shift+` | shift+[Backquote] | | | Ctrl+Shift+Backquote | ~ | Ctrl+Shift+` | | Ctrl+Shift+` | ctrl+shift+` | Ctrl+Shift+` | ctrl+shift+[Backquote] | | -| Alt+Backquote | ` | Alt+` | | Alt+` | alt+` | Alt+` | alt+[Backquote] | | -| Ctrl+Alt+Backquote | ` | Ctrl+Alt+` | | Ctrl+Alt+` | ctrl+alt+` | Ctrl+Alt+` | ctrl+alt+[Backquote] | | -| Shift+Alt+Backquote | ~ | Shift+Alt+` | | Shift+Alt+` | shift+alt+` | Shift+Alt+` | shift+alt+[Backquote] | | -| Ctrl+Shift+Alt+Backquote | ` | Ctrl+Shift+Alt+` | | Ctrl+Shift+Alt+` | ctrl+shift+alt+` | Ctrl+Shift+Alt+` | ctrl+shift+alt+[Backquote] | | +| Alt+Backquote | ` | Alt+` | | Option+` | alt+` | Alt+` | alt+[Backquote] | | +| Ctrl+Alt+Backquote | ` | Ctrl+Alt+` | | Ctrl+Option+` | ctrl+alt+` | Ctrl+Alt+` | ctrl+alt+[Backquote] | | +| Shift+Alt+Backquote | ~ | Shift+Alt+` | | Shift+Option+` | shift+alt+` | Shift+Alt+` | shift+alt+[Backquote] | | +| Ctrl+Shift+Alt+Backquote | ` | Ctrl+Shift+Alt+` | | Ctrl+Shift+Option+` | ctrl+shift+alt+` | Ctrl+Shift+Alt+` | ctrl+shift+alt+[Backquote] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Comma | , | , | | , | , | , | [Comma] | | | Ctrl+Comma | , | Ctrl+, | | Ctrl+, | ctrl+, | Ctrl+, | ctrl+[Comma] | | | Shift+Comma | < | Shift+, | | Shift+, | shift+, | Shift+, | shift+[Comma] | | | Ctrl+Shift+Comma | < | Ctrl+Shift+, | | Ctrl+Shift+, | ctrl+shift+, | Ctrl+Shift+, | ctrl+shift+[Comma] | | -| Alt+Comma | , | Alt+, | | Alt+, | alt+, | Alt+, | alt+[Comma] | | -| Ctrl+Alt+Comma | ≤ | Ctrl+Alt+, | | Ctrl+Alt+, | ctrl+alt+, | Ctrl+Alt+, | ctrl+alt+[Comma] | | -| Shift+Alt+Comma | < | Shift+Alt+, | | Shift+Alt+, | shift+alt+, | Shift+Alt+, | shift+alt+[Comma] | | -| Ctrl+Shift+Alt+Comma | ¯ | Ctrl+Shift+Alt+, | | Ctrl+Shift+Alt+, | ctrl+shift+alt+, | Ctrl+Shift+Alt+, | ctrl+shift+alt+[Comma] | | +| Alt+Comma | , | Alt+, | | Option+, | alt+, | Alt+, | alt+[Comma] | | +| Ctrl+Alt+Comma | ≤ | Ctrl+Alt+, | | Ctrl+Option+, | ctrl+alt+, | Ctrl+Alt+, | ctrl+alt+[Comma] | | +| Shift+Alt+Comma | < | Shift+Alt+, | | Shift+Option+, | shift+alt+, | Shift+Alt+, | shift+alt+[Comma] | | +| Ctrl+Shift+Alt+Comma | ¯ | Ctrl+Shift+Alt+, | | Ctrl+Shift+Option+, | ctrl+shift+alt+, | Ctrl+Shift+Alt+, | ctrl+shift+alt+[Comma] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Period | . | . | | . | . | . | [Period] | | | Ctrl+Period | . | Ctrl+. | | Ctrl+. | ctrl+. | Ctrl+. | ctrl+[Period] | | | Shift+Period | > | Shift+. | | Shift+. | shift+. | Shift+. | shift+[Period] | | | Ctrl+Shift+Period | > | Ctrl+Shift+. | | Ctrl+Shift+. | ctrl+shift+. | Ctrl+Shift+. | ctrl+shift+[Period] | | -| Alt+Period | . | Alt+. | | Alt+. | alt+. | Alt+. | alt+[Period] | | -| Ctrl+Alt+Period | ≥ | Ctrl+Alt+. | | Ctrl+Alt+. | ctrl+alt+. | Ctrl+Alt+. | ctrl+alt+[Period] | | -| Shift+Alt+Period | > | Shift+Alt+. | | Shift+Alt+. | shift+alt+. | Shift+Alt+. | shift+alt+[Period] | | -| Ctrl+Shift+Alt+Period | ˘ | Ctrl+Shift+Alt+. | | Ctrl+Shift+Alt+. | ctrl+shift+alt+. | Ctrl+Shift+Alt+. | ctrl+shift+alt+[Period] | | +| Alt+Period | . | Alt+. | | Option+. | alt+. | Alt+. | alt+[Period] | | +| Ctrl+Alt+Period | ≥ | Ctrl+Alt+. | | Ctrl+Option+. | ctrl+alt+. | Ctrl+Alt+. | ctrl+alt+[Period] | | +| Shift+Alt+Period | > | Shift+Alt+. | | Shift+Option+. | shift+alt+. | Shift+Alt+. | shift+alt+[Period] | | +| Ctrl+Shift+Alt+Period | ˘ | Ctrl+Shift+Alt+. | | Ctrl+Shift+Option+. | ctrl+shift+alt+. | Ctrl+Shift+Alt+. | ctrl+shift+alt+[Period] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Slash | / | / | | / | / | / | [Slash] | | | Ctrl+Slash | / | Ctrl+/ | | Ctrl+/ | ctrl+/ | Ctrl+/ | ctrl+[Slash] | | | Shift+Slash | ? | Shift+/ | | Shift+/ | shift+/ | Shift+/ | shift+[Slash] | | | Ctrl+Shift+Slash | ? | Ctrl+Shift+/ | | Ctrl+Shift+/ | ctrl+shift+/ | Ctrl+Shift+/ | ctrl+shift+[Slash] | | -| Alt+Slash | / | Alt+/ | | Alt+/ | alt+/ | Alt+/ | alt+[Slash] | | -| Ctrl+Alt+Slash | ÷ | Ctrl+Alt+/ | | Ctrl+Alt+/ | ctrl+alt+/ | Ctrl+Alt+/ | ctrl+alt+[Slash] | | -| Shift+Alt+Slash | ? | Shift+Alt+/ | | Shift+Alt+/ | shift+alt+/ | Shift+Alt+/ | shift+alt+[Slash] | | -| Ctrl+Shift+Alt+Slash | ¿ | Ctrl+Shift+Alt+/ | | Ctrl+Shift+Alt+/ | ctrl+shift+alt+/ | Ctrl+Shift+Alt+/ | ctrl+shift+alt+[Slash] | | +| Alt+Slash | / | Alt+/ | | Option+/ | alt+/ | Alt+/ | alt+[Slash] | | +| Ctrl+Alt+Slash | ÷ | Ctrl+Alt+/ | | Ctrl+Option+/ | ctrl+alt+/ | Ctrl+Alt+/ | ctrl+alt+[Slash] | | +| Shift+Alt+Slash | ? | Shift+Alt+/ | | Shift+Option+/ | shift+alt+/ | Shift+Alt+/ | shift+alt+[Slash] | | +| Ctrl+Shift+Alt+Slash | ¿ | Ctrl+Shift+Alt+/ | | Ctrl+Shift+Option+/ | ctrl+shift+alt+/ | Ctrl+Shift+Alt+/ | ctrl+shift+alt+[Slash] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -462,28 +462,28 @@ isUSStandard: true | Ctrl+ArrowUp | --- | Ctrl+UpArrow | | Ctrl+UpArrow | ctrl+up | Ctrl+Up | ctrl+[ArrowUp] | | | Shift+ArrowUp | --- | Shift+UpArrow | | Shift+UpArrow | shift+up | Shift+Up | shift+[ArrowUp] | | | Ctrl+Shift+ArrowUp | --- | Ctrl+Shift+UpArrow | | Ctrl+Shift+UpArrow | ctrl+shift+up | Ctrl+Shift+Up | ctrl+shift+[ArrowUp] | | -| Alt+ArrowUp | --- | Alt+UpArrow | | Alt+UpArrow | alt+up | Alt+Up | alt+[ArrowUp] | | -| Ctrl+Alt+ArrowUp | --- | Ctrl+Alt+UpArrow | | Ctrl+Alt+UpArrow | ctrl+alt+up | Ctrl+Alt+Up | ctrl+alt+[ArrowUp] | | -| Shift+Alt+ArrowUp | --- | Shift+Alt+UpArrow | | Shift+Alt+UpArrow | shift+alt+up | Shift+Alt+Up | shift+alt+[ArrowUp] | | -| Ctrl+Shift+Alt+ArrowUp | --- | Ctrl+Shift+Alt+UpArrow | | Ctrl+Shift+Alt+UpArrow | ctrl+shift+alt+up | Ctrl+Shift+Alt+Up | ctrl+shift+alt+[ArrowUp] | | +| Alt+ArrowUp | --- | Alt+UpArrow | | Option+UpArrow | alt+up | Alt+Up | alt+[ArrowUp] | | +| Ctrl+Alt+ArrowUp | --- | Ctrl+Alt+UpArrow | | Ctrl+Option+UpArrow | ctrl+alt+up | Ctrl+Alt+Up | ctrl+alt+[ArrowUp] | | +| Shift+Alt+ArrowUp | --- | Shift+Alt+UpArrow | | Shift+Option+UpArrow | shift+alt+up | Shift+Alt+Up | shift+alt+[ArrowUp] | | +| Ctrl+Shift+Alt+ArrowUp | --- | Ctrl+Shift+Alt+UpArrow | | Ctrl+Shift+Option+UpArrow | ctrl+shift+alt+up | Ctrl+Shift+Alt+Up | ctrl+shift+alt+[ArrowUp] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Numpad0 | --- | NumPad0 | | NumPad0 | numpad0 | null | [Numpad0] | | | Ctrl+Numpad0 | --- | Ctrl+NumPad0 | | Ctrl+NumPad0 | ctrl+numpad0 | null | ctrl+[Numpad0] | | | Shift+Numpad0 | --- | Shift+NumPad0 | | Shift+NumPad0 | shift+numpad0 | null | shift+[Numpad0] | | | Ctrl+Shift+Numpad0 | --- | Ctrl+Shift+NumPad0 | | Ctrl+Shift+NumPad0 | ctrl+shift+numpad0 | null | ctrl+shift+[Numpad0] | | -| Alt+Numpad0 | --- | Alt+NumPad0 | | Alt+NumPad0 | alt+numpad0 | null | alt+[Numpad0] | | -| Ctrl+Alt+Numpad0 | --- | Ctrl+Alt+NumPad0 | | Ctrl+Alt+NumPad0 | ctrl+alt+numpad0 | null | ctrl+alt+[Numpad0] | | -| Shift+Alt+Numpad0 | --- | Shift+Alt+NumPad0 | | Shift+Alt+NumPad0 | shift+alt+numpad0 | null | shift+alt+[Numpad0] | | -| Ctrl+Shift+Alt+Numpad0 | --- | Ctrl+Shift+Alt+NumPad0 | | Ctrl+Shift+Alt+NumPad0 | ctrl+shift+alt+numpad0 | null | ctrl+shift+alt+[Numpad0] | | +| Alt+Numpad0 | --- | Alt+NumPad0 | | Option+NumPad0 | alt+numpad0 | null | alt+[Numpad0] | | +| Ctrl+Alt+Numpad0 | --- | Ctrl+Alt+NumPad0 | | Ctrl+Option+NumPad0 | ctrl+alt+numpad0 | null | ctrl+alt+[Numpad0] | | +| Shift+Alt+Numpad0 | --- | Shift+Alt+NumPad0 | | Shift+Option+NumPad0 | shift+alt+numpad0 | null | shift+alt+[Numpad0] | | +| Ctrl+Shift+Alt+Numpad0 | --- | Ctrl+Shift+Alt+NumPad0 | | Ctrl+Shift+Option+NumPad0 | ctrl+shift+alt+numpad0 | null | ctrl+shift+alt+[Numpad0] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | IntlBackslash | § | | | § | [IntlBackslash] | null | [IntlBackslash] | NO | | Ctrl+IntlBackslash | § | | | Ctrl+§ | ctrl+[IntlBackslash] | null | ctrl+[IntlBackslash] | NO | | Shift+IntlBackslash | ± | | | Shift+§ | shift+[IntlBackslash] | null | shift+[IntlBackslash] | NO | | Ctrl+Shift+IntlBackslash | ± | | | Ctrl+Shift+§ | ctrl+shift+[IntlBackslash] | null | ctrl+shift+[IntlBackslash] | NO | -| Alt+IntlBackslash | § | | | Alt+§ | alt+[IntlBackslash] | null | alt+[IntlBackslash] | NO | -| Ctrl+Alt+IntlBackslash | § | | | Ctrl+Alt+§ | ctrl+alt+[IntlBackslash] | null | ctrl+alt+[IntlBackslash] | NO | -| Shift+Alt+IntlBackslash | ± | | | Shift+Alt+§ | shift+alt+[IntlBackslash] | null | shift+alt+[IntlBackslash] | NO | -| Ctrl+Shift+Alt+IntlBackslash | ± | | | Ctrl+Shift+Alt+§ | ctrl+shift+alt+[IntlBackslash] | null | ctrl+shift+alt+[IntlBackslash] | NO | +| Alt+IntlBackslash | § | | | Option+§ | alt+[IntlBackslash] | null | alt+[IntlBackslash] | NO | +| Ctrl+Alt+IntlBackslash | § | | | Ctrl+Option+§ | ctrl+alt+[IntlBackslash] | null | ctrl+alt+[IntlBackslash] | NO | +| Shift+Alt+IntlBackslash | ± | | | Shift+Option+§ | shift+alt+[IntlBackslash] | null | shift+alt+[IntlBackslash] | NO | +| Ctrl+Shift+Alt+IntlBackslash | ± | | | Ctrl+Shift+Option+§ | ctrl+shift+alt+[IntlBackslash] | null | ctrl+shift+alt+[IntlBackslash] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | IntlRo | --- | | | null | [IntlRo] | null | [IntlRo] | NO | | Ctrl+IntlRo | --- | | | null | ctrl+[IntlRo] | null | ctrl+[IntlRo] | NO | diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/mac_zh_hant.txt b/src/vs/workbench/services/keybinding/test/electron-browser/mac_zh_hant.txt index e4ebfca03a8..0d7b9b6bbd6 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/mac_zh_hant.txt +++ b/src/vs/workbench/services/keybinding/test/electron-browser/mac_zh_hant.txt @@ -6,37 +6,37 @@ isUSStandard: false | Ctrl+KeyA | ㄇ | Ctrl+A | | Ctrl+A | ctrl+a | Ctrl+A | ctrl+[KeyA] | | | Shift+KeyA | A | Shift+A | | Shift+A | shift+a | Shift+A | shift+[KeyA] | | | Ctrl+Shift+KeyA | A | Ctrl+Shift+A | | Ctrl+Shift+A | ctrl+shift+a | Ctrl+Shift+A | ctrl+shift+[KeyA] | | -| Alt+KeyA | ㄇ | Alt+A | | Alt+A | alt+a | Alt+A | alt+[KeyA] | | -| Ctrl+Alt+KeyA | a | Ctrl+Alt+A | | Ctrl+Alt+A | ctrl+alt+a | Ctrl+Alt+A | ctrl+alt+[KeyA] | | -| Shift+Alt+KeyA | A | Shift+Alt+A | | Shift+Alt+A | shift+alt+a | Shift+Alt+A | shift+alt+[KeyA] | | -| Ctrl+Shift+Alt+KeyA | A | Ctrl+Shift+Alt+A | | Ctrl+Shift+Alt+A | ctrl+shift+alt+a | Ctrl+Shift+Alt+A | ctrl+shift+alt+[KeyA] | | +| Alt+KeyA | ㄇ | Alt+A | | Option+A | alt+a | Alt+A | alt+[KeyA] | | +| Ctrl+Alt+KeyA | a | Ctrl+Alt+A | | Ctrl+Option+A | ctrl+alt+a | Ctrl+Alt+A | ctrl+alt+[KeyA] | | +| Shift+Alt+KeyA | A | Shift+Alt+A | | Shift+Option+A | shift+alt+a | Shift+Alt+A | shift+alt+[KeyA] | | +| Ctrl+Shift+Alt+KeyA | A | Ctrl+Shift+Alt+A | | Ctrl+Shift+Option+A | ctrl+shift+alt+a | Ctrl+Shift+Alt+A | ctrl+shift+alt+[KeyA] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyB | ㄖ | B | | B | b | B | [KeyB] | | | Ctrl+KeyB | ㄖ | Ctrl+B | | Ctrl+B | ctrl+b | Ctrl+B | ctrl+[KeyB] | | | Shift+KeyB | B | Shift+B | | Shift+B | shift+b | Shift+B | shift+[KeyB] | | | Ctrl+Shift+KeyB | B | Ctrl+Shift+B | | Ctrl+Shift+B | ctrl+shift+b | Ctrl+Shift+B | ctrl+shift+[KeyB] | | -| Alt+KeyB | ㄖ | Alt+B | | Alt+B | alt+b | Alt+B | alt+[KeyB] | | -| Ctrl+Alt+KeyB | b | Ctrl+Alt+B | | Ctrl+Alt+B | ctrl+alt+b | Ctrl+Alt+B | ctrl+alt+[KeyB] | | -| Shift+Alt+KeyB | B | Shift+Alt+B | | Shift+Alt+B | shift+alt+b | Shift+Alt+B | shift+alt+[KeyB] | | -| Ctrl+Shift+Alt+KeyB | B | Ctrl+Shift+Alt+B | | Ctrl+Shift+Alt+B | ctrl+shift+alt+b | Ctrl+Shift+Alt+B | ctrl+shift+alt+[KeyB] | | +| Alt+KeyB | ㄖ | Alt+B | | Option+B | alt+b | Alt+B | alt+[KeyB] | | +| Ctrl+Alt+KeyB | b | Ctrl+Alt+B | | Ctrl+Option+B | ctrl+alt+b | Ctrl+Alt+B | ctrl+alt+[KeyB] | | +| Shift+Alt+KeyB | B | Shift+Alt+B | | Shift+Option+B | shift+alt+b | Shift+Alt+B | shift+alt+[KeyB] | | +| Ctrl+Shift+Alt+KeyB | B | Ctrl+Shift+Alt+B | | Ctrl+Shift+Option+B | ctrl+shift+alt+b | Ctrl+Shift+Alt+B | ctrl+shift+alt+[KeyB] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyC | ㄏ | C | | C | c | C | [KeyC] | | | Ctrl+KeyC | ㄏ | Ctrl+C | | Ctrl+C | ctrl+c | Ctrl+C | ctrl+[KeyC] | | | Shift+KeyC | C | Shift+C | | Shift+C | shift+c | Shift+C | shift+[KeyC] | | | Ctrl+Shift+KeyC | C | Ctrl+Shift+C | | Ctrl+Shift+C | ctrl+shift+c | Ctrl+Shift+C | ctrl+shift+[KeyC] | | -| Alt+KeyC | ㄏ | Alt+C | | Alt+C | alt+c | Alt+C | alt+[KeyC] | | -| Ctrl+Alt+KeyC | c | Ctrl+Alt+C | | Ctrl+Alt+C | ctrl+alt+c | Ctrl+Alt+C | ctrl+alt+[KeyC] | | -| Shift+Alt+KeyC | C | Shift+Alt+C | | Shift+Alt+C | shift+alt+c | Shift+Alt+C | shift+alt+[KeyC] | | -| Ctrl+Shift+Alt+KeyC | C | Ctrl+Shift+Alt+C | | Ctrl+Shift+Alt+C | ctrl+shift+alt+c | Ctrl+Shift+Alt+C | ctrl+shift+alt+[KeyC] | | +| Alt+KeyC | ㄏ | Alt+C | | Option+C | alt+c | Alt+C | alt+[KeyC] | | +| Ctrl+Alt+KeyC | c | Ctrl+Alt+C | | Ctrl+Option+C | ctrl+alt+c | Ctrl+Alt+C | ctrl+alt+[KeyC] | | +| Shift+Alt+KeyC | C | Shift+Alt+C | | Shift+Option+C | shift+alt+c | Shift+Alt+C | shift+alt+[KeyC] | | +| Ctrl+Shift+Alt+KeyC | C | Ctrl+Shift+Alt+C | | Ctrl+Shift+Option+C | ctrl+shift+alt+c | Ctrl+Shift+Alt+C | ctrl+shift+alt+[KeyC] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyD | ㄎ | D | | D | d | D | [KeyD] | | | Ctrl+KeyD | ㄎ | Ctrl+D | | Ctrl+D | ctrl+d | Ctrl+D | ctrl+[KeyD] | | | Shift+KeyD | D | Shift+D | | Shift+D | shift+d | Shift+D | shift+[KeyD] | | | Ctrl+Shift+KeyD | D | Ctrl+Shift+D | | Ctrl+Shift+D | ctrl+shift+d | Ctrl+Shift+D | ctrl+shift+[KeyD] | | -| Alt+KeyD | ㄎ | Alt+D | | Alt+D | alt+d | Alt+D | alt+[KeyD] | | -| Ctrl+Alt+KeyD | d | Ctrl+Alt+D | | Ctrl+Alt+D | ctrl+alt+d | Ctrl+Alt+D | ctrl+alt+[KeyD] | | -| Shift+Alt+KeyD | D | Shift+Alt+D | | Shift+Alt+D | shift+alt+d | Shift+Alt+D | shift+alt+[KeyD] | | -| Ctrl+Shift+Alt+KeyD | D | Ctrl+Shift+Alt+D | | Ctrl+Shift+Alt+D | ctrl+shift+alt+d | Ctrl+Shift+Alt+D | ctrl+shift+alt+[KeyD] | | +| Alt+KeyD | ㄎ | Alt+D | | Option+D | alt+d | Alt+D | alt+[KeyD] | | +| Ctrl+Alt+KeyD | d | Ctrl+Alt+D | | Ctrl+Option+D | ctrl+alt+d | Ctrl+Alt+D | ctrl+alt+[KeyD] | | +| Shift+Alt+KeyD | D | Shift+Alt+D | | Shift+Option+D | shift+alt+d | Shift+Alt+D | shift+alt+[KeyD] | | +| Ctrl+Shift+Alt+KeyD | D | Ctrl+Shift+Alt+D | | Ctrl+Shift+Option+D | ctrl+shift+alt+d | Ctrl+Shift+Alt+D | ctrl+shift+alt+[KeyD] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -44,37 +44,37 @@ isUSStandard: false | Ctrl+KeyE | ㄍ | Ctrl+E | | Ctrl+E | ctrl+e | Ctrl+E | ctrl+[KeyE] | | | Shift+KeyE | E | Shift+E | | Shift+E | shift+e | Shift+E | shift+[KeyE] | | | Ctrl+Shift+KeyE | E | Ctrl+Shift+E | | Ctrl+Shift+E | ctrl+shift+e | Ctrl+Shift+E | ctrl+shift+[KeyE] | | -| Alt+KeyE | ㄍ | Alt+E | | Alt+E | alt+e | Alt+E | alt+[KeyE] | | -| Ctrl+Alt+KeyE | e | Ctrl+Alt+E | | Ctrl+Alt+E | ctrl+alt+e | Ctrl+Alt+E | ctrl+alt+[KeyE] | | -| Shift+Alt+KeyE | E | Shift+Alt+E | | Shift+Alt+E | shift+alt+e | Shift+Alt+E | shift+alt+[KeyE] | | -| Ctrl+Shift+Alt+KeyE | E | Ctrl+Shift+Alt+E | | Ctrl+Shift+Alt+E | ctrl+shift+alt+e | Ctrl+Shift+Alt+E | ctrl+shift+alt+[KeyE] | | +| Alt+KeyE | ㄍ | Alt+E | | Option+E | alt+e | Alt+E | alt+[KeyE] | | +| Ctrl+Alt+KeyE | e | Ctrl+Alt+E | | Ctrl+Option+E | ctrl+alt+e | Ctrl+Alt+E | ctrl+alt+[KeyE] | | +| Shift+Alt+KeyE | E | Shift+Alt+E | | Shift+Option+E | shift+alt+e | Shift+Alt+E | shift+alt+[KeyE] | | +| Ctrl+Shift+Alt+KeyE | E | Ctrl+Shift+Alt+E | | Ctrl+Shift+Option+E | ctrl+shift+alt+e | Ctrl+Shift+Alt+E | ctrl+shift+alt+[KeyE] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyF | ㄑ | F | | F | f | F | [KeyF] | | | Ctrl+KeyF | ㄑ | Ctrl+F | | Ctrl+F | ctrl+f | Ctrl+F | ctrl+[KeyF] | | | Shift+KeyF | F | Shift+F | | Shift+F | shift+f | Shift+F | shift+[KeyF] | | | Ctrl+Shift+KeyF | F | Ctrl+Shift+F | | Ctrl+Shift+F | ctrl+shift+f | Ctrl+Shift+F | ctrl+shift+[KeyF] | | -| Alt+KeyF | ㄑ | Alt+F | | Alt+F | alt+f | Alt+F | alt+[KeyF] | | -| Ctrl+Alt+KeyF | f | Ctrl+Alt+F | | Ctrl+Alt+F | ctrl+alt+f | Ctrl+Alt+F | ctrl+alt+[KeyF] | | -| Shift+Alt+KeyF | F | Shift+Alt+F | | Shift+Alt+F | shift+alt+f | Shift+Alt+F | shift+alt+[KeyF] | | -| Ctrl+Shift+Alt+KeyF | F | Ctrl+Shift+Alt+F | | Ctrl+Shift+Alt+F | ctrl+shift+alt+f | Ctrl+Shift+Alt+F | ctrl+shift+alt+[KeyF] | | +| Alt+KeyF | ㄑ | Alt+F | | Option+F | alt+f | Alt+F | alt+[KeyF] | | +| Ctrl+Alt+KeyF | f | Ctrl+Alt+F | | Ctrl+Option+F | ctrl+alt+f | Ctrl+Alt+F | ctrl+alt+[KeyF] | | +| Shift+Alt+KeyF | F | Shift+Alt+F | | Shift+Option+F | shift+alt+f | Shift+Alt+F | shift+alt+[KeyF] | | +| Ctrl+Shift+Alt+KeyF | F | Ctrl+Shift+Alt+F | | Ctrl+Shift+Option+F | ctrl+shift+alt+f | Ctrl+Shift+Alt+F | ctrl+shift+alt+[KeyF] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyG | ㄕ | G | | G | g | G | [KeyG] | | | Ctrl+KeyG | ㄕ | Ctrl+G | | Ctrl+G | ctrl+g | Ctrl+G | ctrl+[KeyG] | | | Shift+KeyG | G | Shift+G | | Shift+G | shift+g | Shift+G | shift+[KeyG] | | | Ctrl+Shift+KeyG | G | Ctrl+Shift+G | | Ctrl+Shift+G | ctrl+shift+g | Ctrl+Shift+G | ctrl+shift+[KeyG] | | -| Alt+KeyG | ㄕ | Alt+G | | Alt+G | alt+g | Alt+G | alt+[KeyG] | | -| Ctrl+Alt+KeyG | g | Ctrl+Alt+G | | Ctrl+Alt+G | ctrl+alt+g | Ctrl+Alt+G | ctrl+alt+[KeyG] | | -| Shift+Alt+KeyG | G | Shift+Alt+G | | Shift+Alt+G | shift+alt+g | Shift+Alt+G | shift+alt+[KeyG] | | -| Ctrl+Shift+Alt+KeyG | G | Ctrl+Shift+Alt+G | | Ctrl+Shift+Alt+G | ctrl+shift+alt+g | Ctrl+Shift+Alt+G | ctrl+shift+alt+[KeyG] | | +| Alt+KeyG | ㄕ | Alt+G | | Option+G | alt+g | Alt+G | alt+[KeyG] | | +| Ctrl+Alt+KeyG | g | Ctrl+Alt+G | | Ctrl+Option+G | ctrl+alt+g | Ctrl+Alt+G | ctrl+alt+[KeyG] | | +| Shift+Alt+KeyG | G | Shift+Alt+G | | Shift+Option+G | shift+alt+g | Shift+Alt+G | shift+alt+[KeyG] | | +| Ctrl+Shift+Alt+KeyG | G | Ctrl+Shift+Alt+G | | Ctrl+Shift+Option+G | ctrl+shift+alt+g | Ctrl+Shift+Alt+G | ctrl+shift+alt+[KeyG] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyH | ㄘ | H | | H | h | H | [KeyH] | | | Ctrl+KeyH | ㄘ | Ctrl+H | | Ctrl+H | ctrl+h | Ctrl+H | ctrl+[KeyH] | | | Shift+KeyH | H | Shift+H | | Shift+H | shift+h | Shift+H | shift+[KeyH] | | | Ctrl+Shift+KeyH | H | Ctrl+Shift+H | | Ctrl+Shift+H | ctrl+shift+h | Ctrl+Shift+H | ctrl+shift+[KeyH] | | -| Alt+KeyH | ㄘ | Alt+H | | Alt+H | alt+h | Alt+H | alt+[KeyH] | | -| Ctrl+Alt+KeyH | h | Ctrl+Alt+H | | Ctrl+Alt+H | ctrl+alt+h | Ctrl+Alt+H | ctrl+alt+[KeyH] | | -| Shift+Alt+KeyH | H | Shift+Alt+H | | Shift+Alt+H | shift+alt+h | Shift+Alt+H | shift+alt+[KeyH] | | -| Ctrl+Shift+Alt+KeyH | H | Ctrl+Shift+Alt+H | | Ctrl+Shift+Alt+H | ctrl+shift+alt+h | Ctrl+Shift+Alt+H | ctrl+shift+alt+[KeyH] | | +| Alt+KeyH | ㄘ | Alt+H | | Option+H | alt+h | Alt+H | alt+[KeyH] | | +| Ctrl+Alt+KeyH | h | Ctrl+Alt+H | | Ctrl+Option+H | ctrl+alt+h | Ctrl+Alt+H | ctrl+alt+[KeyH] | | +| Shift+Alt+KeyH | H | Shift+Alt+H | | Shift+Option+H | shift+alt+h | Shift+Alt+H | shift+alt+[KeyH] | | +| Ctrl+Shift+Alt+KeyH | H | Ctrl+Shift+Alt+H | | Ctrl+Shift+Option+H | ctrl+shift+alt+h | Ctrl+Shift+Alt+H | ctrl+shift+alt+[KeyH] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -82,37 +82,37 @@ isUSStandard: false | Ctrl+KeyI | ㄛ | Ctrl+I | | Ctrl+I | ctrl+i | Ctrl+I | ctrl+[KeyI] | | | Shift+KeyI | I | Shift+I | | Shift+I | shift+i | Shift+I | shift+[KeyI] | | | Ctrl+Shift+KeyI | I | Ctrl+Shift+I | | Ctrl+Shift+I | ctrl+shift+i | Ctrl+Shift+I | ctrl+shift+[KeyI] | | -| Alt+KeyI | ㄛ | Alt+I | | Alt+I | alt+i | Alt+I | alt+[KeyI] | | -| Ctrl+Alt+KeyI | i | Ctrl+Alt+I | | Ctrl+Alt+I | ctrl+alt+i | Ctrl+Alt+I | ctrl+alt+[KeyI] | | -| Shift+Alt+KeyI | I | Shift+Alt+I | | Shift+Alt+I | shift+alt+i | Shift+Alt+I | shift+alt+[KeyI] | | -| Ctrl+Shift+Alt+KeyI | I | Ctrl+Shift+Alt+I | | Ctrl+Shift+Alt+I | ctrl+shift+alt+i | Ctrl+Shift+Alt+I | ctrl+shift+alt+[KeyI] | | +| Alt+KeyI | ㄛ | Alt+I | | Option+I | alt+i | Alt+I | alt+[KeyI] | | +| Ctrl+Alt+KeyI | i | Ctrl+Alt+I | | Ctrl+Option+I | ctrl+alt+i | Ctrl+Alt+I | ctrl+alt+[KeyI] | | +| Shift+Alt+KeyI | I | Shift+Alt+I | | Shift+Option+I | shift+alt+i | Shift+Alt+I | shift+alt+[KeyI] | | +| Ctrl+Shift+Alt+KeyI | I | Ctrl+Shift+Alt+I | | Ctrl+Shift+Option+I | ctrl+shift+alt+i | Ctrl+Shift+Alt+I | ctrl+shift+alt+[KeyI] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyJ | ㄨ | J | | J | j | J | [KeyJ] | | | Ctrl+KeyJ | ㄨ | Ctrl+J | | Ctrl+J | ctrl+j | Ctrl+J | ctrl+[KeyJ] | | | Shift+KeyJ | J | Shift+J | | Shift+J | shift+j | Shift+J | shift+[KeyJ] | | | Ctrl+Shift+KeyJ | J | Ctrl+Shift+J | | Ctrl+Shift+J | ctrl+shift+j | Ctrl+Shift+J | ctrl+shift+[KeyJ] | | -| Alt+KeyJ | ㄨ | Alt+J | | Alt+J | alt+j | Alt+J | alt+[KeyJ] | | -| Ctrl+Alt+KeyJ | j | Ctrl+Alt+J | | Ctrl+Alt+J | ctrl+alt+j | Ctrl+Alt+J | ctrl+alt+[KeyJ] | | -| Shift+Alt+KeyJ | J | Shift+Alt+J | | Shift+Alt+J | shift+alt+j | Shift+Alt+J | shift+alt+[KeyJ] | | -| Ctrl+Shift+Alt+KeyJ | J | Ctrl+Shift+Alt+J | | Ctrl+Shift+Alt+J | ctrl+shift+alt+j | Ctrl+Shift+Alt+J | ctrl+shift+alt+[KeyJ] | | +| Alt+KeyJ | ㄨ | Alt+J | | Option+J | alt+j | Alt+J | alt+[KeyJ] | | +| Ctrl+Alt+KeyJ | j | Ctrl+Alt+J | | Ctrl+Option+J | ctrl+alt+j | Ctrl+Alt+J | ctrl+alt+[KeyJ] | | +| Shift+Alt+KeyJ | J | Shift+Alt+J | | Shift+Option+J | shift+alt+j | Shift+Alt+J | shift+alt+[KeyJ] | | +| Ctrl+Shift+Alt+KeyJ | J | Ctrl+Shift+Alt+J | | Ctrl+Shift+Option+J | ctrl+shift+alt+j | Ctrl+Shift+Alt+J | ctrl+shift+alt+[KeyJ] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyK | ㄜ | K | | K | k | K | [KeyK] | | | Ctrl+KeyK | ㄜ | Ctrl+K | | Ctrl+K | ctrl+k | Ctrl+K | ctrl+[KeyK] | | | Shift+KeyK | K | Shift+K | | Shift+K | shift+k | Shift+K | shift+[KeyK] | | | Ctrl+Shift+KeyK | K | Ctrl+Shift+K | | Ctrl+Shift+K | ctrl+shift+k | Ctrl+Shift+K | ctrl+shift+[KeyK] | | -| Alt+KeyK | ㄜ | Alt+K | | Alt+K | alt+k | Alt+K | alt+[KeyK] | | -| Ctrl+Alt+KeyK | k | Ctrl+Alt+K | | Ctrl+Alt+K | ctrl+alt+k | Ctrl+Alt+K | ctrl+alt+[KeyK] | | -| Shift+Alt+KeyK | K | Shift+Alt+K | | Shift+Alt+K | shift+alt+k | Shift+Alt+K | shift+alt+[KeyK] | | -| Ctrl+Shift+Alt+KeyK | K | Ctrl+Shift+Alt+K | | Ctrl+Shift+Alt+K | ctrl+shift+alt+k | Ctrl+Shift+Alt+K | ctrl+shift+alt+[KeyK] | | +| Alt+KeyK | ㄜ | Alt+K | | Option+K | alt+k | Alt+K | alt+[KeyK] | | +| Ctrl+Alt+KeyK | k | Ctrl+Alt+K | | Ctrl+Option+K | ctrl+alt+k | Ctrl+Alt+K | ctrl+alt+[KeyK] | | +| Shift+Alt+KeyK | K | Shift+Alt+K | | Shift+Option+K | shift+alt+k | Shift+Alt+K | shift+alt+[KeyK] | | +| Ctrl+Shift+Alt+KeyK | K | Ctrl+Shift+Alt+K | | Ctrl+Shift+Option+K | ctrl+shift+alt+k | Ctrl+Shift+Alt+K | ctrl+shift+alt+[KeyK] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyL | ㄠ | L | | L | l | L | [KeyL] | | | Ctrl+KeyL | ㄠ | Ctrl+L | | Ctrl+L | ctrl+l | Ctrl+L | ctrl+[KeyL] | | | Shift+KeyL | L | Shift+L | | Shift+L | shift+l | Shift+L | shift+[KeyL] | | | Ctrl+Shift+KeyL | L | Ctrl+Shift+L | | Ctrl+Shift+L | ctrl+shift+l | Ctrl+Shift+L | ctrl+shift+[KeyL] | | -| Alt+KeyL | ㄠ | Alt+L | | Alt+L | alt+l | Alt+L | alt+[KeyL] | | -| Ctrl+Alt+KeyL | l | Ctrl+Alt+L | | Ctrl+Alt+L | ctrl+alt+l | Ctrl+Alt+L | ctrl+alt+[KeyL] | | -| Shift+Alt+KeyL | L | Shift+Alt+L | | Shift+Alt+L | shift+alt+l | Shift+Alt+L | shift+alt+[KeyL] | | -| Ctrl+Shift+Alt+KeyL | L | Ctrl+Shift+Alt+L | | Ctrl+Shift+Alt+L | ctrl+shift+alt+l | Ctrl+Shift+Alt+L | ctrl+shift+alt+[KeyL] | | +| Alt+KeyL | ㄠ | Alt+L | | Option+L | alt+l | Alt+L | alt+[KeyL] | | +| Ctrl+Alt+KeyL | l | Ctrl+Alt+L | | Ctrl+Option+L | ctrl+alt+l | Ctrl+Alt+L | ctrl+alt+[KeyL] | | +| Shift+Alt+KeyL | L | Shift+Alt+L | | Shift+Option+L | shift+alt+l | Shift+Alt+L | shift+alt+[KeyL] | | +| Ctrl+Shift+Alt+KeyL | L | Ctrl+Shift+Alt+L | | Ctrl+Shift+Option+L | ctrl+shift+alt+l | Ctrl+Shift+Alt+L | ctrl+shift+alt+[KeyL] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -120,37 +120,37 @@ isUSStandard: false | Ctrl+KeyM | ㄩ | Ctrl+M | | Ctrl+M | ctrl+m | Ctrl+M | ctrl+[KeyM] | | | Shift+KeyM | M | Shift+M | | Shift+M | shift+m | Shift+M | shift+[KeyM] | | | Ctrl+Shift+KeyM | M | Ctrl+Shift+M | | Ctrl+Shift+M | ctrl+shift+m | Ctrl+Shift+M | ctrl+shift+[KeyM] | | -| Alt+KeyM | ㄩ | Alt+M | | Alt+M | alt+m | Alt+M | alt+[KeyM] | | -| Ctrl+Alt+KeyM | m | Ctrl+Alt+M | | Ctrl+Alt+M | ctrl+alt+m | Ctrl+Alt+M | ctrl+alt+[KeyM] | | -| Shift+Alt+KeyM | M | Shift+Alt+M | | Shift+Alt+M | shift+alt+m | Shift+Alt+M | shift+alt+[KeyM] | | -| Ctrl+Shift+Alt+KeyM | M | Ctrl+Shift+Alt+M | | Ctrl+Shift+Alt+M | ctrl+shift+alt+m | Ctrl+Shift+Alt+M | ctrl+shift+alt+[KeyM] | | +| Alt+KeyM | ㄩ | Alt+M | | Option+M | alt+m | Alt+M | alt+[KeyM] | | +| Ctrl+Alt+KeyM | m | Ctrl+Alt+M | | Ctrl+Option+M | ctrl+alt+m | Ctrl+Alt+M | ctrl+alt+[KeyM] | | +| Shift+Alt+KeyM | M | Shift+Alt+M | | Shift+Option+M | shift+alt+m | Shift+Alt+M | shift+alt+[KeyM] | | +| Ctrl+Shift+Alt+KeyM | M | Ctrl+Shift+Alt+M | | Ctrl+Shift+Option+M | ctrl+shift+alt+m | Ctrl+Shift+Alt+M | ctrl+shift+alt+[KeyM] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyN | ㄙ | N | | N | n | N | [KeyN] | | | Ctrl+KeyN | ㄙ | Ctrl+N | | Ctrl+N | ctrl+n | Ctrl+N | ctrl+[KeyN] | | | Shift+KeyN | N | Shift+N | | Shift+N | shift+n | Shift+N | shift+[KeyN] | | | Ctrl+Shift+KeyN | N | Ctrl+Shift+N | | Ctrl+Shift+N | ctrl+shift+n | Ctrl+Shift+N | ctrl+shift+[KeyN] | | -| Alt+KeyN | ㄙ | Alt+N | | Alt+N | alt+n | Alt+N | alt+[KeyN] | | -| Ctrl+Alt+KeyN | n | Ctrl+Alt+N | | Ctrl+Alt+N | ctrl+alt+n | Ctrl+Alt+N | ctrl+alt+[KeyN] | | -| Shift+Alt+KeyN | N | Shift+Alt+N | | Shift+Alt+N | shift+alt+n | Shift+Alt+N | shift+alt+[KeyN] | | -| Ctrl+Shift+Alt+KeyN | N | Ctrl+Shift+Alt+N | | Ctrl+Shift+Alt+N | ctrl+shift+alt+n | Ctrl+Shift+Alt+N | ctrl+shift+alt+[KeyN] | | +| Alt+KeyN | ㄙ | Alt+N | | Option+N | alt+n | Alt+N | alt+[KeyN] | | +| Ctrl+Alt+KeyN | n | Ctrl+Alt+N | | Ctrl+Option+N | ctrl+alt+n | Ctrl+Alt+N | ctrl+alt+[KeyN] | | +| Shift+Alt+KeyN | N | Shift+Alt+N | | Shift+Option+N | shift+alt+n | Shift+Alt+N | shift+alt+[KeyN] | | +| Ctrl+Shift+Alt+KeyN | N | Ctrl+Shift+Alt+N | | Ctrl+Shift+Option+N | ctrl+shift+alt+n | Ctrl+Shift+Alt+N | ctrl+shift+alt+[KeyN] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyO | ㄟ | O | | O | o | O | [KeyO] | | | Ctrl+KeyO | ㄟ | Ctrl+O | | Ctrl+O | ctrl+o | Ctrl+O | ctrl+[KeyO] | | | Shift+KeyO | O | Shift+O | | Shift+O | shift+o | Shift+O | shift+[KeyO] | | | Ctrl+Shift+KeyO | O | Ctrl+Shift+O | | Ctrl+Shift+O | ctrl+shift+o | Ctrl+Shift+O | ctrl+shift+[KeyO] | | -| Alt+KeyO | ㄟ | Alt+O | | Alt+O | alt+o | Alt+O | alt+[KeyO] | | -| Ctrl+Alt+KeyO | o | Ctrl+Alt+O | | Ctrl+Alt+O | ctrl+alt+o | Ctrl+Alt+O | ctrl+alt+[KeyO] | | -| Shift+Alt+KeyO | O | Shift+Alt+O | | Shift+Alt+O | shift+alt+o | Shift+Alt+O | shift+alt+[KeyO] | | -| Ctrl+Shift+Alt+KeyO | O | Ctrl+Shift+Alt+O | | Ctrl+Shift+Alt+O | ctrl+shift+alt+o | Ctrl+Shift+Alt+O | ctrl+shift+alt+[KeyO] | | +| Alt+KeyO | ㄟ | Alt+O | | Option+O | alt+o | Alt+O | alt+[KeyO] | | +| Ctrl+Alt+KeyO | o | Ctrl+Alt+O | | Ctrl+Option+O | ctrl+alt+o | Ctrl+Alt+O | ctrl+alt+[KeyO] | | +| Shift+Alt+KeyO | O | Shift+Alt+O | | Shift+Option+O | shift+alt+o | Shift+Alt+O | shift+alt+[KeyO] | | +| Ctrl+Shift+Alt+KeyO | O | Ctrl+Shift+Alt+O | | Ctrl+Shift+Option+O | ctrl+shift+alt+o | Ctrl+Shift+Alt+O | ctrl+shift+alt+[KeyO] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyP | ㄣ | P | | P | p | P | [KeyP] | | | Ctrl+KeyP | ㄣ | Ctrl+P | | Ctrl+P | ctrl+p | Ctrl+P | ctrl+[KeyP] | | | Shift+KeyP | P | Shift+P | | Shift+P | shift+p | Shift+P | shift+[KeyP] | | | Ctrl+Shift+KeyP | P | Ctrl+Shift+P | | Ctrl+Shift+P | ctrl+shift+p | Ctrl+Shift+P | ctrl+shift+[KeyP] | | -| Alt+KeyP | ㄣ | Alt+P | | Alt+P | alt+p | Alt+P | alt+[KeyP] | | -| Ctrl+Alt+KeyP | p | Ctrl+Alt+P | | Ctrl+Alt+P | ctrl+alt+p | Ctrl+Alt+P | ctrl+alt+[KeyP] | | -| Shift+Alt+KeyP | P | Shift+Alt+P | | Shift+Alt+P | shift+alt+p | Shift+Alt+P | shift+alt+[KeyP] | | -| Ctrl+Shift+Alt+KeyP | P | Ctrl+Shift+Alt+P | | Ctrl+Shift+Alt+P | ctrl+shift+alt+p | Ctrl+Shift+Alt+P | ctrl+shift+alt+[KeyP] | | +| Alt+KeyP | ㄣ | Alt+P | | Option+P | alt+p | Alt+P | alt+[KeyP] | | +| Ctrl+Alt+KeyP | p | Ctrl+Alt+P | | Ctrl+Option+P | ctrl+alt+p | Ctrl+Alt+P | ctrl+alt+[KeyP] | | +| Shift+Alt+KeyP | P | Shift+Alt+P | | Shift+Option+P | shift+alt+p | Shift+Alt+P | shift+alt+[KeyP] | | +| Ctrl+Shift+Alt+KeyP | P | Ctrl+Shift+Alt+P | | Ctrl+Shift+Option+P | ctrl+shift+alt+p | Ctrl+Shift+Alt+P | ctrl+shift+alt+[KeyP] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -158,37 +158,37 @@ isUSStandard: false | Ctrl+KeyQ | ㄆ | Ctrl+Q | | Ctrl+Q | ctrl+q | Ctrl+Q | ctrl+[KeyQ] | | | Shift+KeyQ | Q | Shift+Q | | Shift+Q | shift+q | Shift+Q | shift+[KeyQ] | | | Ctrl+Shift+KeyQ | Q | Ctrl+Shift+Q | | Ctrl+Shift+Q | ctrl+shift+q | Ctrl+Shift+Q | ctrl+shift+[KeyQ] | | -| Alt+KeyQ | ㄆ | Alt+Q | | Alt+Q | alt+q | Alt+Q | alt+[KeyQ] | | -| Ctrl+Alt+KeyQ | q | Ctrl+Alt+Q | | Ctrl+Alt+Q | ctrl+alt+q | Ctrl+Alt+Q | ctrl+alt+[KeyQ] | | -| Shift+Alt+KeyQ | Q | Shift+Alt+Q | | Shift+Alt+Q | shift+alt+q | Shift+Alt+Q | shift+alt+[KeyQ] | | -| Ctrl+Shift+Alt+KeyQ | Q | Ctrl+Shift+Alt+Q | | Ctrl+Shift+Alt+Q | ctrl+shift+alt+q | Ctrl+Shift+Alt+Q | ctrl+shift+alt+[KeyQ] | | +| Alt+KeyQ | ㄆ | Alt+Q | | Option+Q | alt+q | Alt+Q | alt+[KeyQ] | | +| Ctrl+Alt+KeyQ | q | Ctrl+Alt+Q | | Ctrl+Option+Q | ctrl+alt+q | Ctrl+Alt+Q | ctrl+alt+[KeyQ] | | +| Shift+Alt+KeyQ | Q | Shift+Alt+Q | | Shift+Option+Q | shift+alt+q | Shift+Alt+Q | shift+alt+[KeyQ] | | +| Ctrl+Shift+Alt+KeyQ | Q | Ctrl+Shift+Alt+Q | | Ctrl+Shift+Option+Q | ctrl+shift+alt+q | Ctrl+Shift+Alt+Q | ctrl+shift+alt+[KeyQ] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyR | ㄐ | R | | R | r | R | [KeyR] | | | Ctrl+KeyR | ㄐ | Ctrl+R | | Ctrl+R | ctrl+r | Ctrl+R | ctrl+[KeyR] | | | Shift+KeyR | R | Shift+R | | Shift+R | shift+r | Shift+R | shift+[KeyR] | | | Ctrl+Shift+KeyR | R | Ctrl+Shift+R | | Ctrl+Shift+R | ctrl+shift+r | Ctrl+Shift+R | ctrl+shift+[KeyR] | | -| Alt+KeyR | ㄐ | Alt+R | | Alt+R | alt+r | Alt+R | alt+[KeyR] | | -| Ctrl+Alt+KeyR | r | Ctrl+Alt+R | | Ctrl+Alt+R | ctrl+alt+r | Ctrl+Alt+R | ctrl+alt+[KeyR] | | -| Shift+Alt+KeyR | R | Shift+Alt+R | | Shift+Alt+R | shift+alt+r | Shift+Alt+R | shift+alt+[KeyR] | | -| Ctrl+Shift+Alt+KeyR | R | Ctrl+Shift+Alt+R | | Ctrl+Shift+Alt+R | ctrl+shift+alt+r | Ctrl+Shift+Alt+R | ctrl+shift+alt+[KeyR] | | +| Alt+KeyR | ㄐ | Alt+R | | Option+R | alt+r | Alt+R | alt+[KeyR] | | +| Ctrl+Alt+KeyR | r | Ctrl+Alt+R | | Ctrl+Option+R | ctrl+alt+r | Ctrl+Alt+R | ctrl+alt+[KeyR] | | +| Shift+Alt+KeyR | R | Shift+Alt+R | | Shift+Option+R | shift+alt+r | Shift+Alt+R | shift+alt+[KeyR] | | +| Ctrl+Shift+Alt+KeyR | R | Ctrl+Shift+Alt+R | | Ctrl+Shift+Option+R | ctrl+shift+alt+r | Ctrl+Shift+Alt+R | ctrl+shift+alt+[KeyR] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyS | ㄋ | S | | S | s | S | [KeyS] | | | Ctrl+KeyS | ㄋ | Ctrl+S | | Ctrl+S | ctrl+s | Ctrl+S | ctrl+[KeyS] | | | Shift+KeyS | S | Shift+S | | Shift+S | shift+s | Shift+S | shift+[KeyS] | | | Ctrl+Shift+KeyS | S | Ctrl+Shift+S | | Ctrl+Shift+S | ctrl+shift+s | Ctrl+Shift+S | ctrl+shift+[KeyS] | | -| Alt+KeyS | ㄋ | Alt+S | | Alt+S | alt+s | Alt+S | alt+[KeyS] | | -| Ctrl+Alt+KeyS | s | Ctrl+Alt+S | | Ctrl+Alt+S | ctrl+alt+s | Ctrl+Alt+S | ctrl+alt+[KeyS] | | -| Shift+Alt+KeyS | S | Shift+Alt+S | | Shift+Alt+S | shift+alt+s | Shift+Alt+S | shift+alt+[KeyS] | | -| Ctrl+Shift+Alt+KeyS | S | Ctrl+Shift+Alt+S | | Ctrl+Shift+Alt+S | ctrl+shift+alt+s | Ctrl+Shift+Alt+S | ctrl+shift+alt+[KeyS] | | +| Alt+KeyS | ㄋ | Alt+S | | Option+S | alt+s | Alt+S | alt+[KeyS] | | +| Ctrl+Alt+KeyS | s | Ctrl+Alt+S | | Ctrl+Option+S | ctrl+alt+s | Ctrl+Alt+S | ctrl+alt+[KeyS] | | +| Shift+Alt+KeyS | S | Shift+Alt+S | | Shift+Option+S | shift+alt+s | Shift+Alt+S | shift+alt+[KeyS] | | +| Ctrl+Shift+Alt+KeyS | S | Ctrl+Shift+Alt+S | | Ctrl+Shift+Option+S | ctrl+shift+alt+s | Ctrl+Shift+Alt+S | ctrl+shift+alt+[KeyS] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyT | ㄔ | T | | T | t | T | [KeyT] | | | Ctrl+KeyT | ㄔ | Ctrl+T | | Ctrl+T | ctrl+t | Ctrl+T | ctrl+[KeyT] | | | Shift+KeyT | T | Shift+T | | Shift+T | shift+t | Shift+T | shift+[KeyT] | | | Ctrl+Shift+KeyT | T | Ctrl+Shift+T | | Ctrl+Shift+T | ctrl+shift+t | Ctrl+Shift+T | ctrl+shift+[KeyT] | | -| Alt+KeyT | ㄔ | Alt+T | | Alt+T | alt+t | Alt+T | alt+[KeyT] | | -| Ctrl+Alt+KeyT | t | Ctrl+Alt+T | | Ctrl+Alt+T | ctrl+alt+t | Ctrl+Alt+T | ctrl+alt+[KeyT] | | -| Shift+Alt+KeyT | T | Shift+Alt+T | | Shift+Alt+T | shift+alt+t | Shift+Alt+T | shift+alt+[KeyT] | | -| Ctrl+Shift+Alt+KeyT | T | Ctrl+Shift+Alt+T | | Ctrl+Shift+Alt+T | ctrl+shift+alt+t | Ctrl+Shift+Alt+T | ctrl+shift+alt+[KeyT] | | +| Alt+KeyT | ㄔ | Alt+T | | Option+T | alt+t | Alt+T | alt+[KeyT] | | +| Ctrl+Alt+KeyT | t | Ctrl+Alt+T | | Ctrl+Option+T | ctrl+alt+t | Ctrl+Alt+T | ctrl+alt+[KeyT] | | +| Shift+Alt+KeyT | T | Shift+Alt+T | | Shift+Option+T | shift+alt+t | Shift+Alt+T | shift+alt+[KeyT] | | +| Ctrl+Shift+Alt+KeyT | T | Ctrl+Shift+Alt+T | | Ctrl+Shift+Option+T | ctrl+shift+alt+t | Ctrl+Shift+Alt+T | ctrl+shift+alt+[KeyT] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -196,37 +196,37 @@ isUSStandard: false | Ctrl+KeyU | ㄧ | Ctrl+U | | Ctrl+U | ctrl+u | Ctrl+U | ctrl+[KeyU] | | | Shift+KeyU | U | Shift+U | | Shift+U | shift+u | Shift+U | shift+[KeyU] | | | Ctrl+Shift+KeyU | U | Ctrl+Shift+U | | Ctrl+Shift+U | ctrl+shift+u | Ctrl+Shift+U | ctrl+shift+[KeyU] | | -| Alt+KeyU | ㄧ | Alt+U | | Alt+U | alt+u | Alt+U | alt+[KeyU] | | -| Ctrl+Alt+KeyU | u | Ctrl+Alt+U | | Ctrl+Alt+U | ctrl+alt+u | Ctrl+Alt+U | ctrl+alt+[KeyU] | | -| Shift+Alt+KeyU | U | Shift+Alt+U | | Shift+Alt+U | shift+alt+u | Shift+Alt+U | shift+alt+[KeyU] | | -| Ctrl+Shift+Alt+KeyU | U | Ctrl+Shift+Alt+U | | Ctrl+Shift+Alt+U | ctrl+shift+alt+u | Ctrl+Shift+Alt+U | ctrl+shift+alt+[KeyU] | | +| Alt+KeyU | ㄧ | Alt+U | | Option+U | alt+u | Alt+U | alt+[KeyU] | | +| Ctrl+Alt+KeyU | u | Ctrl+Alt+U | | Ctrl+Option+U | ctrl+alt+u | Ctrl+Alt+U | ctrl+alt+[KeyU] | | +| Shift+Alt+KeyU | U | Shift+Alt+U | | Shift+Option+U | shift+alt+u | Shift+Alt+U | shift+alt+[KeyU] | | +| Ctrl+Shift+Alt+KeyU | U | Ctrl+Shift+Alt+U | | Ctrl+Shift+Option+U | ctrl+shift+alt+u | Ctrl+Shift+Alt+U | ctrl+shift+alt+[KeyU] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyV | ㄒ | V | | V | v | V | [KeyV] | | | Ctrl+KeyV | ㄒ | Ctrl+V | | Ctrl+V | ctrl+v | Ctrl+V | ctrl+[KeyV] | | | Shift+KeyV | V | Shift+V | | Shift+V | shift+v | Shift+V | shift+[KeyV] | | | Ctrl+Shift+KeyV | V | Ctrl+Shift+V | | Ctrl+Shift+V | ctrl+shift+v | Ctrl+Shift+V | ctrl+shift+[KeyV] | | -| Alt+KeyV | ㄒ | Alt+V | | Alt+V | alt+v | Alt+V | alt+[KeyV] | | -| Ctrl+Alt+KeyV | v | Ctrl+Alt+V | | Ctrl+Alt+V | ctrl+alt+v | Ctrl+Alt+V | ctrl+alt+[KeyV] | | -| Shift+Alt+KeyV | V | Shift+Alt+V | | Shift+Alt+V | shift+alt+v | Shift+Alt+V | shift+alt+[KeyV] | | -| Ctrl+Shift+Alt+KeyV | V | Ctrl+Shift+Alt+V | | Ctrl+Shift+Alt+V | ctrl+shift+alt+v | Ctrl+Shift+Alt+V | ctrl+shift+alt+[KeyV] | | +| Alt+KeyV | ㄒ | Alt+V | | Option+V | alt+v | Alt+V | alt+[KeyV] | | +| Ctrl+Alt+KeyV | v | Ctrl+Alt+V | | Ctrl+Option+V | ctrl+alt+v | Ctrl+Alt+V | ctrl+alt+[KeyV] | | +| Shift+Alt+KeyV | V | Shift+Alt+V | | Shift+Option+V | shift+alt+v | Shift+Alt+V | shift+alt+[KeyV] | | +| Ctrl+Shift+Alt+KeyV | V | Ctrl+Shift+Alt+V | | Ctrl+Shift+Option+V | ctrl+shift+alt+v | Ctrl+Shift+Alt+V | ctrl+shift+alt+[KeyV] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyW | ㄊ | W | | W | w | W | [KeyW] | | | Ctrl+KeyW | ㄊ | Ctrl+W | | Ctrl+W | ctrl+w | Ctrl+W | ctrl+[KeyW] | | | Shift+KeyW | W | Shift+W | | Shift+W | shift+w | Shift+W | shift+[KeyW] | | | Ctrl+Shift+KeyW | W | Ctrl+Shift+W | | Ctrl+Shift+W | ctrl+shift+w | Ctrl+Shift+W | ctrl+shift+[KeyW] | | -| Alt+KeyW | ㄊ | Alt+W | | Alt+W | alt+w | Alt+W | alt+[KeyW] | | -| Ctrl+Alt+KeyW | w | Ctrl+Alt+W | | Ctrl+Alt+W | ctrl+alt+w | Ctrl+Alt+W | ctrl+alt+[KeyW] | | -| Shift+Alt+KeyW | W | Shift+Alt+W | | Shift+Alt+W | shift+alt+w | Shift+Alt+W | shift+alt+[KeyW] | | -| Ctrl+Shift+Alt+KeyW | W | Ctrl+Shift+Alt+W | | Ctrl+Shift+Alt+W | ctrl+shift+alt+w | Ctrl+Shift+Alt+W | ctrl+shift+alt+[KeyW] | | +| Alt+KeyW | ㄊ | Alt+W | | Option+W | alt+w | Alt+W | alt+[KeyW] | | +| Ctrl+Alt+KeyW | w | Ctrl+Alt+W | | Ctrl+Option+W | ctrl+alt+w | Ctrl+Alt+W | ctrl+alt+[KeyW] | | +| Shift+Alt+KeyW | W | Shift+Alt+W | | Shift+Option+W | shift+alt+w | Shift+Alt+W | shift+alt+[KeyW] | | +| Ctrl+Shift+Alt+KeyW | W | Ctrl+Shift+Alt+W | | Ctrl+Shift+Option+W | ctrl+shift+alt+w | Ctrl+Shift+Alt+W | ctrl+shift+alt+[KeyW] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyX | ㄌ | X | | X | x | X | [KeyX] | | | Ctrl+KeyX | ㄌ | Ctrl+X | | Ctrl+X | ctrl+x | Ctrl+X | ctrl+[KeyX] | | | Shift+KeyX | X | Shift+X | | Shift+X | shift+x | Shift+X | shift+[KeyX] | | | Ctrl+Shift+KeyX | X | Ctrl+Shift+X | | Ctrl+Shift+X | ctrl+shift+x | Ctrl+Shift+X | ctrl+shift+[KeyX] | | -| Alt+KeyX | ㄌ | Alt+X | | Alt+X | alt+x | Alt+X | alt+[KeyX] | | -| Ctrl+Alt+KeyX | x | Ctrl+Alt+X | | Ctrl+Alt+X | ctrl+alt+x | Ctrl+Alt+X | ctrl+alt+[KeyX] | | -| Shift+Alt+KeyX | X | Shift+Alt+X | | Shift+Alt+X | shift+alt+x | Shift+Alt+X | shift+alt+[KeyX] | | -| Ctrl+Shift+Alt+KeyX | X | Ctrl+Shift+Alt+X | | Ctrl+Shift+Alt+X | ctrl+shift+alt+x | Ctrl+Shift+Alt+X | ctrl+shift+alt+[KeyX] | | +| Alt+KeyX | ㄌ | Alt+X | | Option+X | alt+x | Alt+X | alt+[KeyX] | | +| Ctrl+Alt+KeyX | x | Ctrl+Alt+X | | Ctrl+Option+X | ctrl+alt+x | Ctrl+Alt+X | ctrl+alt+[KeyX] | | +| Shift+Alt+KeyX | X | Shift+Alt+X | | Shift+Option+X | shift+alt+x | Shift+Alt+X | shift+alt+[KeyX] | | +| Ctrl+Shift+Alt+KeyX | X | Ctrl+Shift+Alt+X | | Ctrl+Shift+Option+X | ctrl+shift+alt+x | Ctrl+Shift+Alt+X | ctrl+shift+alt+[KeyX] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -234,37 +234,37 @@ isUSStandard: false | Ctrl+KeyY | ㄗ | Ctrl+Y | | Ctrl+Y | ctrl+y | Ctrl+Y | ctrl+[KeyY] | | | Shift+KeyY | Y | Shift+Y | | Shift+Y | shift+y | Shift+Y | shift+[KeyY] | | | Ctrl+Shift+KeyY | Y | Ctrl+Shift+Y | | Ctrl+Shift+Y | ctrl+shift+y | Ctrl+Shift+Y | ctrl+shift+[KeyY] | | -| Alt+KeyY | ㄗ | Alt+Y | | Alt+Y | alt+y | Alt+Y | alt+[KeyY] | | -| Ctrl+Alt+KeyY | y | Ctrl+Alt+Y | | Ctrl+Alt+Y | ctrl+alt+y | Ctrl+Alt+Y | ctrl+alt+[KeyY] | | -| Shift+Alt+KeyY | Y | Shift+Alt+Y | | Shift+Alt+Y | shift+alt+y | Shift+Alt+Y | shift+alt+[KeyY] | | -| Ctrl+Shift+Alt+KeyY | Y | Ctrl+Shift+Alt+Y | | Ctrl+Shift+Alt+Y | ctrl+shift+alt+y | Ctrl+Shift+Alt+Y | ctrl+shift+alt+[KeyY] | | +| Alt+KeyY | ㄗ | Alt+Y | | Option+Y | alt+y | Alt+Y | alt+[KeyY] | | +| Ctrl+Alt+KeyY | y | Ctrl+Alt+Y | | Ctrl+Option+Y | ctrl+alt+y | Ctrl+Alt+Y | ctrl+alt+[KeyY] | | +| Shift+Alt+KeyY | Y | Shift+Alt+Y | | Shift+Option+Y | shift+alt+y | Shift+Alt+Y | shift+alt+[KeyY] | | +| Ctrl+Shift+Alt+KeyY | Y | Ctrl+Shift+Alt+Y | | Ctrl+Shift+Option+Y | ctrl+shift+alt+y | Ctrl+Shift+Alt+Y | ctrl+shift+alt+[KeyY] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyZ | ㄈ | Z | | Z | z | Z | [KeyZ] | | | Ctrl+KeyZ | ㄈ | Ctrl+Z | | Ctrl+Z | ctrl+z | Ctrl+Z | ctrl+[KeyZ] | | | Shift+KeyZ | Z | Shift+Z | | Shift+Z | shift+z | Shift+Z | shift+[KeyZ] | | | Ctrl+Shift+KeyZ | Z | Ctrl+Shift+Z | | Ctrl+Shift+Z | ctrl+shift+z | Ctrl+Shift+Z | ctrl+shift+[KeyZ] | | -| Alt+KeyZ | ㄈ | Alt+Z | | Alt+Z | alt+z | Alt+Z | alt+[KeyZ] | | -| Ctrl+Alt+KeyZ | z | Ctrl+Alt+Z | | Ctrl+Alt+Z | ctrl+alt+z | Ctrl+Alt+Z | ctrl+alt+[KeyZ] | | -| Shift+Alt+KeyZ | Z | Shift+Alt+Z | | Shift+Alt+Z | shift+alt+z | Shift+Alt+Z | shift+alt+[KeyZ] | | -| Ctrl+Shift+Alt+KeyZ | Z | Ctrl+Shift+Alt+Z | | Ctrl+Shift+Alt+Z | ctrl+shift+alt+z | Ctrl+Shift+Alt+Z | ctrl+shift+alt+[KeyZ] | | +| Alt+KeyZ | ㄈ | Alt+Z | | Option+Z | alt+z | Alt+Z | alt+[KeyZ] | | +| Ctrl+Alt+KeyZ | z | Ctrl+Alt+Z | | Ctrl+Option+Z | ctrl+alt+z | Ctrl+Alt+Z | ctrl+alt+[KeyZ] | | +| Shift+Alt+KeyZ | Z | Shift+Alt+Z | | Shift+Option+Z | shift+alt+z | Shift+Alt+Z | shift+alt+[KeyZ] | | +| Ctrl+Shift+Alt+KeyZ | Z | Ctrl+Shift+Alt+Z | | Ctrl+Shift+Option+Z | ctrl+shift+alt+z | Ctrl+Shift+Alt+Z | ctrl+shift+alt+[KeyZ] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit1 | ㄅ | 1 | | ㄅ | 1 | 1 | [Digit1] | NO | | Ctrl+Digit1 | ㄅ | Ctrl+1 | | Ctrl+ㄅ | ctrl+1 | Ctrl+1 | ctrl+[Digit1] | NO | | Shift+Digit1 | ! | Shift+1 | | Shift+ㄅ | shift+1 | Shift+1 | shift+[Digit1] | NO | | Ctrl+Shift+Digit1 | ! | Ctrl+Shift+1 | | Ctrl+Shift+ㄅ | ctrl+shift+1 | Ctrl+Shift+1 | ctrl+shift+[Digit1] | NO | -| Alt+Digit1 | ㄅ | Alt+1 | | Alt+ㄅ | alt+1 | Alt+1 | alt+[Digit1] | NO | -| Ctrl+Alt+Digit1 | 1 | Ctrl+Alt+1 | | Ctrl+Alt+ㄅ | ctrl+alt+1 | Ctrl+Alt+1 | ctrl+alt+[Digit1] | NO | -| Shift+Alt+Digit1 | ! | Shift+Alt+1 | | Shift+Alt+ㄅ | shift+alt+1 | Shift+Alt+1 | shift+alt+[Digit1] | NO | -| Ctrl+Shift+Alt+Digit1 | ! | Ctrl+Shift+Alt+1 | | Ctrl+Shift+Alt+ㄅ | ctrl+shift+alt+1 | Ctrl+Shift+Alt+1 | ctrl+shift+alt+[Digit1] | NO | +| Alt+Digit1 | ㄅ | Alt+1 | | Option+ㄅ | alt+1 | Alt+1 | alt+[Digit1] | NO | +| Ctrl+Alt+Digit1 | 1 | Ctrl+Alt+1 | | Ctrl+Option+ㄅ | ctrl+alt+1 | Ctrl+Alt+1 | ctrl+alt+[Digit1] | NO | +| Shift+Alt+Digit1 | ! | Shift+Alt+1 | | Shift+Option+ㄅ | shift+alt+1 | Shift+Alt+1 | shift+alt+[Digit1] | NO | +| Ctrl+Shift+Alt+Digit1 | ! | Ctrl+Shift+Alt+1 | | Ctrl+Shift+Option+ㄅ | ctrl+shift+alt+1 | Ctrl+Shift+Alt+1 | ctrl+shift+alt+[Digit1] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit2 | ㄉ | 2 | | ㄉ | 2 | 2 | [Digit2] | NO | | Ctrl+Digit2 | ㄉ | Ctrl+2 | | Ctrl+ㄉ | ctrl+2 | Ctrl+2 | ctrl+[Digit2] | NO | | Shift+Digit2 | @ | Shift+2 | | Shift+ㄉ | shift+2 | Shift+2 | shift+[Digit2] | NO | | Ctrl+Shift+Digit2 | @ | Ctrl+Shift+2 | | Ctrl+Shift+ㄉ | ctrl+shift+2 | Ctrl+Shift+2 | ctrl+shift+[Digit2] | NO | -| Alt+Digit2 | ㄉ | Alt+2 | | Alt+ㄉ | alt+2 | Alt+2 | alt+[Digit2] | NO | -| Ctrl+Alt+Digit2 | 2 | Ctrl+Alt+2 | | Ctrl+Alt+ㄉ | ctrl+alt+2 | Ctrl+Alt+2 | ctrl+alt+[Digit2] | NO | -| Shift+Alt+Digit2 | @ | Shift+Alt+2 | | Shift+Alt+ㄉ | shift+alt+2 | Shift+Alt+2 | shift+alt+[Digit2] | NO | -| Ctrl+Shift+Alt+Digit2 | @ | Ctrl+Shift+Alt+2 | | Ctrl+Shift+Alt+ㄉ | ctrl+shift+alt+2 | Ctrl+Shift+Alt+2 | ctrl+shift+alt+[Digit2] | NO | +| Alt+Digit2 | ㄉ | Alt+2 | | Option+ㄉ | alt+2 | Alt+2 | alt+[Digit2] | NO | +| Ctrl+Alt+Digit2 | 2 | Ctrl+Alt+2 | | Ctrl+Option+ㄉ | ctrl+alt+2 | Ctrl+Alt+2 | ctrl+alt+[Digit2] | NO | +| Shift+Alt+Digit2 | @ | Shift+Alt+2 | | Shift+Option+ㄉ | shift+alt+2 | Shift+Alt+2 | shift+alt+[Digit2] | NO | +| Ctrl+Shift+Alt+Digit2 | @ | Ctrl+Shift+Alt+2 | | Ctrl+Shift+Option+ㄉ | ctrl+shift+alt+2 | Ctrl+Shift+Alt+2 | ctrl+shift+alt+[Digit2] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -272,37 +272,37 @@ isUSStandard: false | Ctrl+Digit3 | ˇ | Ctrl+3 | | Ctrl+ˇ | ctrl+3 | Ctrl+3 | ctrl+[Digit3] | NO | | Shift+Digit3 | # | Shift+3 | | Shift+ˇ | shift+3 | Shift+3 | shift+[Digit3] | NO | | Ctrl+Shift+Digit3 | # | Ctrl+Shift+3 | | Ctrl+Shift+ˇ | ctrl+shift+3 | Ctrl+Shift+3 | ctrl+shift+[Digit3] | NO | -| Alt+Digit3 | ˇ | Alt+3 | | Alt+ˇ | alt+3 | Alt+3 | alt+[Digit3] | NO | -| Ctrl+Alt+Digit3 | 3 | Ctrl+Alt+3 | | Ctrl+Alt+ˇ | ctrl+alt+3 | Ctrl+Alt+3 | ctrl+alt+[Digit3] | NO | -| Shift+Alt+Digit3 | # | Shift+Alt+3 | | Shift+Alt+ˇ | shift+alt+3 | Shift+Alt+3 | shift+alt+[Digit3] | NO | -| Ctrl+Shift+Alt+Digit3 | # | Ctrl+Shift+Alt+3 | | Ctrl+Shift+Alt+ˇ | ctrl+shift+alt+3 | Ctrl+Shift+Alt+3 | ctrl+shift+alt+[Digit3] | NO | +| Alt+Digit3 | ˇ | Alt+3 | | Option+ˇ | alt+3 | Alt+3 | alt+[Digit3] | NO | +| Ctrl+Alt+Digit3 | 3 | Ctrl+Alt+3 | | Ctrl+Option+ˇ | ctrl+alt+3 | Ctrl+Alt+3 | ctrl+alt+[Digit3] | NO | +| Shift+Alt+Digit3 | # | Shift+Alt+3 | | Shift+Option+ˇ | shift+alt+3 | Shift+Alt+3 | shift+alt+[Digit3] | NO | +| Ctrl+Shift+Alt+Digit3 | # | Ctrl+Shift+Alt+3 | | Ctrl+Shift+Option+ˇ | ctrl+shift+alt+3 | Ctrl+Shift+Alt+3 | ctrl+shift+alt+[Digit3] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit4 | ˋ | 4 | | ˋ | 4 | 4 | [Digit4] | NO | | Ctrl+Digit4 | ˋ | Ctrl+4 | | Ctrl+ˋ | ctrl+4 | Ctrl+4 | ctrl+[Digit4] | NO | | Shift+Digit4 | $ | Shift+4 | | Shift+ˋ | shift+4 | Shift+4 | shift+[Digit4] | NO | | Ctrl+Shift+Digit4 | $ | Ctrl+Shift+4 | | Ctrl+Shift+ˋ | ctrl+shift+4 | Ctrl+Shift+4 | ctrl+shift+[Digit4] | NO | -| Alt+Digit4 | ˋ | Alt+4 | | Alt+ˋ | alt+4 | Alt+4 | alt+[Digit4] | NO | -| Ctrl+Alt+Digit4 | 4 | Ctrl+Alt+4 | | Ctrl+Alt+ˋ | ctrl+alt+4 | Ctrl+Alt+4 | ctrl+alt+[Digit4] | NO | -| Shift+Alt+Digit4 | $ | Shift+Alt+4 | | Shift+Alt+ˋ | shift+alt+4 | Shift+Alt+4 | shift+alt+[Digit4] | NO | -| Ctrl+Shift+Alt+Digit4 | $ | Ctrl+Shift+Alt+4 | | Ctrl+Shift+Alt+ˋ | ctrl+shift+alt+4 | Ctrl+Shift+Alt+4 | ctrl+shift+alt+[Digit4] | NO | +| Alt+Digit4 | ˋ | Alt+4 | | Option+ˋ | alt+4 | Alt+4 | alt+[Digit4] | NO | +| Ctrl+Alt+Digit4 | 4 | Ctrl+Alt+4 | | Ctrl+Option+ˋ | ctrl+alt+4 | Ctrl+Alt+4 | ctrl+alt+[Digit4] | NO | +| Shift+Alt+Digit4 | $ | Shift+Alt+4 | | Shift+Option+ˋ | shift+alt+4 | Shift+Alt+4 | shift+alt+[Digit4] | NO | +| Ctrl+Shift+Alt+Digit4 | $ | Ctrl+Shift+Alt+4 | | Ctrl+Shift+Option+ˋ | ctrl+shift+alt+4 | Ctrl+Shift+Alt+4 | ctrl+shift+alt+[Digit4] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit5 | ㄓ | 5 | | ㄓ | 5 | 5 | [Digit5] | NO | | Ctrl+Digit5 | ㄓ | Ctrl+5 | | Ctrl+ㄓ | ctrl+5 | Ctrl+5 | ctrl+[Digit5] | NO | | Shift+Digit5 | % | Shift+5 | | Shift+ㄓ | shift+5 | Shift+5 | shift+[Digit5] | NO | | Ctrl+Shift+Digit5 | % | Ctrl+Shift+5 | | Ctrl+Shift+ㄓ | ctrl+shift+5 | Ctrl+Shift+5 | ctrl+shift+[Digit5] | NO | -| Alt+Digit5 | ㄓ | Alt+5 | | Alt+ㄓ | alt+5 | Alt+5 | alt+[Digit5] | NO | -| Ctrl+Alt+Digit5 | 5 | Ctrl+Alt+5 | | Ctrl+Alt+ㄓ | ctrl+alt+5 | Ctrl+Alt+5 | ctrl+alt+[Digit5] | NO | -| Shift+Alt+Digit5 | % | Shift+Alt+5 | | Shift+Alt+ㄓ | shift+alt+5 | Shift+Alt+5 | shift+alt+[Digit5] | NO | -| Ctrl+Shift+Alt+Digit5 | % | Ctrl+Shift+Alt+5 | | Ctrl+Shift+Alt+ㄓ | ctrl+shift+alt+5 | Ctrl+Shift+Alt+5 | ctrl+shift+alt+[Digit5] | NO | +| Alt+Digit5 | ㄓ | Alt+5 | | Option+ㄓ | alt+5 | Alt+5 | alt+[Digit5] | NO | +| Ctrl+Alt+Digit5 | 5 | Ctrl+Alt+5 | | Ctrl+Option+ㄓ | ctrl+alt+5 | Ctrl+Alt+5 | ctrl+alt+[Digit5] | NO | +| Shift+Alt+Digit5 | % | Shift+Alt+5 | | Shift+Option+ㄓ | shift+alt+5 | Shift+Alt+5 | shift+alt+[Digit5] | NO | +| Ctrl+Shift+Alt+Digit5 | % | Ctrl+Shift+Alt+5 | | Ctrl+Shift+Option+ㄓ | ctrl+shift+alt+5 | Ctrl+Shift+Alt+5 | ctrl+shift+alt+[Digit5] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit6 | ˊ | 6 | | ˊ | 6 | 6 | [Digit6] | NO | | Ctrl+Digit6 | ˊ | Ctrl+6 | | Ctrl+ˊ | ctrl+6 | Ctrl+6 | ctrl+[Digit6] | NO | | Shift+Digit6 | ^ | Shift+6 | | Shift+ˊ | shift+6 | Shift+6 | shift+[Digit6] | NO | | Ctrl+Shift+Digit6 | ^ | Ctrl+Shift+6 | | Ctrl+Shift+ˊ | ctrl+shift+6 | Ctrl+Shift+6 | ctrl+shift+[Digit6] | NO | -| Alt+Digit6 | ˊ | Alt+6 | | Alt+ˊ | alt+6 | Alt+6 | alt+[Digit6] | NO | -| Ctrl+Alt+Digit6 | 6 | Ctrl+Alt+6 | | Ctrl+Alt+ˊ | ctrl+alt+6 | Ctrl+Alt+6 | ctrl+alt+[Digit6] | NO | -| Shift+Alt+Digit6 | ^ | Shift+Alt+6 | | Shift+Alt+ˊ | shift+alt+6 | Shift+Alt+6 | shift+alt+[Digit6] | NO | -| Ctrl+Shift+Alt+Digit6 | ^ | Ctrl+Shift+Alt+6 | | Ctrl+Shift+Alt+ˊ | ctrl+shift+alt+6 | Ctrl+Shift+Alt+6 | ctrl+shift+alt+[Digit6] | NO | +| Alt+Digit6 | ˊ | Alt+6 | | Option+ˊ | alt+6 | Alt+6 | alt+[Digit6] | NO | +| Ctrl+Alt+Digit6 | 6 | Ctrl+Alt+6 | | Ctrl+Option+ˊ | ctrl+alt+6 | Ctrl+Alt+6 | ctrl+alt+[Digit6] | NO | +| Shift+Alt+Digit6 | ^ | Shift+Alt+6 | | Shift+Option+ˊ | shift+alt+6 | Shift+Alt+6 | shift+alt+[Digit6] | NO | +| Ctrl+Shift+Alt+Digit6 | ^ | Ctrl+Shift+Alt+6 | | Ctrl+Shift+Option+ˊ | ctrl+shift+alt+6 | Ctrl+Shift+Alt+6 | ctrl+shift+alt+[Digit6] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -310,37 +310,37 @@ isUSStandard: false | Ctrl+Digit7 | ˙ | Ctrl+7 | | Ctrl+˙ | ctrl+7 | Ctrl+7 | ctrl+[Digit7] | NO | | Shift+Digit7 | & | Shift+7 | | Shift+˙ | shift+7 | Shift+7 | shift+[Digit7] | NO | | Ctrl+Shift+Digit7 | & | Ctrl+Shift+7 | | Ctrl+Shift+˙ | ctrl+shift+7 | Ctrl+Shift+7 | ctrl+shift+[Digit7] | NO | -| Alt+Digit7 | ˙ | Alt+7 | | Alt+˙ | alt+7 | Alt+7 | alt+[Digit7] | NO | -| Ctrl+Alt+Digit7 | 7 | Ctrl+Alt+7 | | Ctrl+Alt+˙ | ctrl+alt+7 | Ctrl+Alt+7 | ctrl+alt+[Digit7] | NO | -| Shift+Alt+Digit7 | & | Shift+Alt+7 | | Shift+Alt+˙ | shift+alt+7 | Shift+Alt+7 | shift+alt+[Digit7] | NO | -| Ctrl+Shift+Alt+Digit7 | & | Ctrl+Shift+Alt+7 | | Ctrl+Shift+Alt+˙ | ctrl+shift+alt+7 | Ctrl+Shift+Alt+7 | ctrl+shift+alt+[Digit7] | NO | +| Alt+Digit7 | ˙ | Alt+7 | | Option+˙ | alt+7 | Alt+7 | alt+[Digit7] | NO | +| Ctrl+Alt+Digit7 | 7 | Ctrl+Alt+7 | | Ctrl+Option+˙ | ctrl+alt+7 | Ctrl+Alt+7 | ctrl+alt+[Digit7] | NO | +| Shift+Alt+Digit7 | & | Shift+Alt+7 | | Shift+Option+˙ | shift+alt+7 | Shift+Alt+7 | shift+alt+[Digit7] | NO | +| Ctrl+Shift+Alt+Digit7 | & | Ctrl+Shift+Alt+7 | | Ctrl+Shift+Option+˙ | ctrl+shift+alt+7 | Ctrl+Shift+Alt+7 | ctrl+shift+alt+[Digit7] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit8 | ㄚ | 8 | | ㄚ | 8 | 8 | [Digit8] | NO | | Ctrl+Digit8 | ㄚ | Ctrl+8 | | Ctrl+ㄚ | ctrl+8 | Ctrl+8 | ctrl+[Digit8] | NO | | Shift+Digit8 | * | Shift+8 | | Shift+ㄚ | shift+8 | Shift+8 | shift+[Digit8] | NO | | Ctrl+Shift+Digit8 | * | Ctrl+Shift+8 | | Ctrl+Shift+ㄚ | ctrl+shift+8 | Ctrl+Shift+8 | ctrl+shift+[Digit8] | NO | -| Alt+Digit8 | ㄚ | Alt+8 | | Alt+ㄚ | alt+8 | Alt+8 | alt+[Digit8] | NO | -| Ctrl+Alt+Digit8 | 8 | Ctrl+Alt+8 | | Ctrl+Alt+ㄚ | ctrl+alt+8 | Ctrl+Alt+8 | ctrl+alt+[Digit8] | NO | -| Shift+Alt+Digit8 | * | Shift+Alt+8 | | Shift+Alt+ㄚ | shift+alt+8 | Shift+Alt+8 | shift+alt+[Digit8] | NO | -| Ctrl+Shift+Alt+Digit8 | * | Ctrl+Shift+Alt+8 | | Ctrl+Shift+Alt+ㄚ | ctrl+shift+alt+8 | Ctrl+Shift+Alt+8 | ctrl+shift+alt+[Digit8] | NO | +| Alt+Digit8 | ㄚ | Alt+8 | | Option+ㄚ | alt+8 | Alt+8 | alt+[Digit8] | NO | +| Ctrl+Alt+Digit8 | 8 | Ctrl+Alt+8 | | Ctrl+Option+ㄚ | ctrl+alt+8 | Ctrl+Alt+8 | ctrl+alt+[Digit8] | NO | +| Shift+Alt+Digit8 | * | Shift+Alt+8 | | Shift+Option+ㄚ | shift+alt+8 | Shift+Alt+8 | shift+alt+[Digit8] | NO | +| Ctrl+Shift+Alt+Digit8 | * | Ctrl+Shift+Alt+8 | | Ctrl+Shift+Option+ㄚ | ctrl+shift+alt+8 | Ctrl+Shift+Alt+8 | ctrl+shift+alt+[Digit8] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit9 | ㄞ | 9 | | ㄞ | 9 | 9 | [Digit9] | NO | | Ctrl+Digit9 | ㄞ | Ctrl+9 | | Ctrl+ㄞ | ctrl+9 | Ctrl+9 | ctrl+[Digit9] | NO | | Shift+Digit9 | ( | Shift+9 | | Shift+ㄞ | shift+9 | Shift+9 | shift+[Digit9] | NO | | Ctrl+Shift+Digit9 | ( | Ctrl+Shift+9 | | Ctrl+Shift+ㄞ | ctrl+shift+9 | Ctrl+Shift+9 | ctrl+shift+[Digit9] | NO | -| Alt+Digit9 | ㄞ | Alt+9 | | Alt+ㄞ | alt+9 | Alt+9 | alt+[Digit9] | NO | -| Ctrl+Alt+Digit9 | 9 | Ctrl+Alt+9 | | Ctrl+Alt+ㄞ | ctrl+alt+9 | Ctrl+Alt+9 | ctrl+alt+[Digit9] | NO | -| Shift+Alt+Digit9 | ( | Shift+Alt+9 | | Shift+Alt+ㄞ | shift+alt+9 | Shift+Alt+9 | shift+alt+[Digit9] | NO | -| Ctrl+Shift+Alt+Digit9 | ( | Ctrl+Shift+Alt+9 | | Ctrl+Shift+Alt+ㄞ | ctrl+shift+alt+9 | Ctrl+Shift+Alt+9 | ctrl+shift+alt+[Digit9] | NO | +| Alt+Digit9 | ㄞ | Alt+9 | | Option+ㄞ | alt+9 | Alt+9 | alt+[Digit9] | NO | +| Ctrl+Alt+Digit9 | 9 | Ctrl+Alt+9 | | Ctrl+Option+ㄞ | ctrl+alt+9 | Ctrl+Alt+9 | ctrl+alt+[Digit9] | NO | +| Shift+Alt+Digit9 | ( | Shift+Alt+9 | | Shift+Option+ㄞ | shift+alt+9 | Shift+Alt+9 | shift+alt+[Digit9] | NO | +| Ctrl+Shift+Alt+Digit9 | ( | Ctrl+Shift+Alt+9 | | Ctrl+Shift+Option+ㄞ | ctrl+shift+alt+9 | Ctrl+Shift+Alt+9 | ctrl+shift+alt+[Digit9] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit0 | ㄢ | 0 | | ㄢ | 0 | 0 | [Digit0] | NO | | Ctrl+Digit0 | ㄢ | Ctrl+0 | | Ctrl+ㄢ | ctrl+0 | Ctrl+0 | ctrl+[Digit0] | NO | | Shift+Digit0 | ) | Shift+0 | | Shift+ㄢ | shift+0 | Shift+0 | shift+[Digit0] | NO | | Ctrl+Shift+Digit0 | ) | Ctrl+Shift+0 | | Ctrl+Shift+ㄢ | ctrl+shift+0 | Ctrl+Shift+0 | ctrl+shift+[Digit0] | NO | -| Alt+Digit0 | ㄢ | Alt+0 | | Alt+ㄢ | alt+0 | Alt+0 | alt+[Digit0] | NO | -| Ctrl+Alt+Digit0 | 0 | Ctrl+Alt+0 | | Ctrl+Alt+ㄢ | ctrl+alt+0 | Ctrl+Alt+0 | ctrl+alt+[Digit0] | NO | -| Shift+Alt+Digit0 | ) | Shift+Alt+0 | | Shift+Alt+ㄢ | shift+alt+0 | Shift+Alt+0 | shift+alt+[Digit0] | NO | -| Ctrl+Shift+Alt+Digit0 | ) | Ctrl+Shift+Alt+0 | | Ctrl+Shift+Alt+ㄢ | ctrl+shift+alt+0 | Ctrl+Shift+Alt+0 | ctrl+shift+alt+[Digit0] | NO | +| Alt+Digit0 | ㄢ | Alt+0 | | Option+ㄢ | alt+0 | Alt+0 | alt+[Digit0] | NO | +| Ctrl+Alt+Digit0 | 0 | Ctrl+Alt+0 | | Ctrl+Option+ㄢ | ctrl+alt+0 | Ctrl+Alt+0 | ctrl+alt+[Digit0] | NO | +| Shift+Alt+Digit0 | ) | Shift+Alt+0 | | Shift+Option+ㄢ | shift+alt+0 | Shift+Alt+0 | shift+alt+[Digit0] | NO | +| Ctrl+Shift+Alt+Digit0 | ) | Ctrl+Shift+Alt+0 | | Ctrl+Shift+Option+ㄢ | ctrl+shift+alt+0 | Ctrl+Shift+Alt+0 | ctrl+shift+alt+[Digit0] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -348,37 +348,37 @@ isUSStandard: false | Ctrl+Minus | ㄦ | | | Ctrl+ㄦ | ctrl+[Minus] | null | ctrl+[Minus] | NO | | Shift+Minus | _ | Shift+- | | Shift+ㄦ | shift+[Minus] | null | shift+[Minus] | NO | | Ctrl+Shift+Minus | _ | Ctrl+Shift+- | | Ctrl+Shift+ㄦ | ctrl+shift+[Minus] | null | ctrl+shift+[Minus] | NO | -| Alt+Minus | ㄦ | | | Alt+ㄦ | alt+[Minus] | null | alt+[Minus] | NO | -| Ctrl+Alt+Minus | — | | | Ctrl+Alt+ㄦ | ctrl+alt+[Minus] | null | ctrl+alt+[Minus] | NO | -| Shift+Alt+Minus | _ | Shift+Alt+- | | Shift+Alt+ㄦ | shift+alt+[Minus] | null | shift+alt+[Minus] | NO | -| Ctrl+Shift+Alt+Minus | _ | Ctrl+Shift+Alt+- | | Ctrl+Shift+Alt+ㄦ | ctrl+shift+alt+[Minus] | null | ctrl+shift+alt+[Minus] | NO | +| Alt+Minus | ㄦ | | | Option+ㄦ | alt+[Minus] | null | alt+[Minus] | NO | +| Ctrl+Alt+Minus | — | | | Ctrl+Option+ㄦ | ctrl+alt+[Minus] | null | ctrl+alt+[Minus] | NO | +| Shift+Alt+Minus | _ | Shift+Alt+- | | Shift+Option+ㄦ | shift+alt+[Minus] | null | shift+alt+[Minus] | NO | +| Ctrl+Shift+Alt+Minus | _ | Ctrl+Shift+Alt+- | | Ctrl+Shift+Option+ㄦ | ctrl+shift+alt+[Minus] | null | ctrl+shift+alt+[Minus] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Equal | = | = | | = | = | = | [Equal] | | | Ctrl+Equal | = | Ctrl+= | | Ctrl+= | ctrl+= | Ctrl+= | ctrl+[Equal] | | | Shift+Equal | + | Shift+= | | Shift+= | shift+= | Shift+= | shift+[Equal] | | | Ctrl+Shift+Equal | + | Ctrl+Shift+= | | Ctrl+Shift+= | ctrl+shift+= | Ctrl+Shift+= | ctrl+shift+[Equal] | | -| Alt+Equal | = | Alt+= | | Alt+= | alt+= | Alt+= | alt+[Equal] | | -| Ctrl+Alt+Equal | = | Ctrl+Alt+= | | Ctrl+Alt+= | ctrl+alt+= | Ctrl+Alt+= | ctrl+alt+[Equal] | | -| Shift+Alt+Equal | + | Shift+Alt+= | | Shift+Alt+= | shift+alt+= | Shift+Alt+= | shift+alt+[Equal] | | -| Ctrl+Shift+Alt+Equal | + | Ctrl+Shift+Alt+= | | Ctrl+Shift+Alt+= | ctrl+shift+alt+= | Ctrl+Shift+Alt+= | ctrl+shift+alt+[Equal] | | +| Alt+Equal | = | Alt+= | | Option+= | alt+= | Alt+= | alt+[Equal] | | +| Ctrl+Alt+Equal | = | Ctrl+Alt+= | | Ctrl+Option+= | ctrl+alt+= | Ctrl+Alt+= | ctrl+alt+[Equal] | | +| Shift+Alt+Equal | + | Shift+Alt+= | | Shift+Option+= | shift+alt+= | Shift+Alt+= | shift+alt+[Equal] | | +| Ctrl+Shift+Alt+Equal | + | Ctrl+Shift+Alt+= | | Ctrl+Shift+Option+= | ctrl+shift+alt+= | Ctrl+Shift+Alt+= | ctrl+shift+alt+[Equal] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | BracketLeft | 「 | [ | 1 | 「 | [ | [ | [BracketLeft] | NO | | Ctrl+BracketLeft | 「 | Ctrl+[ | | Ctrl+「 | ctrl+[ | Ctrl+[ | ctrl+[BracketLeft] | NO | | Shift+BracketLeft | 『 | Shift+[ | 1 | Shift+「 | shift+[ | Shift+[ | shift+[BracketLeft] | NO | | Ctrl+Shift+BracketLeft | 『 | Ctrl+Shift+[ | | Ctrl+Shift+「 | ctrl+shift+[ | Ctrl+Shift+[ | ctrl+shift+[BracketLeft] | NO | -| Alt+BracketLeft | 「 | Alt+[ | | Alt+「 | alt+[ | Alt+[ | alt+[BracketLeft] | NO | -| Ctrl+Alt+BracketLeft | [ | [ | 2 | Ctrl+Alt+「 | ctrl+alt+[BracketLeft] | Ctrl+Alt+[ | ctrl+alt+[BracketLeft] | NO | -| Shift+Alt+BracketLeft | 『 | Shift+Alt+[ | | Shift+Alt+「 | shift+alt+[ | Shift+Alt+[ | shift+alt+[BracketLeft] | NO | -| Ctrl+Shift+Alt+BracketLeft | { | Shift+[ | 2 | Ctrl+Shift+Alt+「 | ctrl+shift+alt+[BracketLeft] | Ctrl+Shift+Alt+[ | ctrl+shift+alt+[BracketLeft] | NO | +| Alt+BracketLeft | 「 | Alt+[ | | Option+「 | alt+[ | Alt+[ | alt+[BracketLeft] | NO | +| Ctrl+Alt+BracketLeft | [ | [ | 2 | Ctrl+Option+「 | ctrl+alt+[BracketLeft] | Ctrl+Alt+[ | ctrl+alt+[BracketLeft] | NO | +| Shift+Alt+BracketLeft | 『 | Shift+Alt+[ | | Shift+Option+「 | shift+alt+[ | Shift+Alt+[ | shift+alt+[BracketLeft] | NO | +| Ctrl+Shift+Alt+BracketLeft | { | Shift+[ | 2 | Ctrl+Shift+Option+「 | ctrl+shift+alt+[BracketLeft] | Ctrl+Shift+Alt+[ | ctrl+shift+alt+[BracketLeft] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | BracketRight | 」 | ] | 1 | 」 | ] | ] | [BracketRight] | NO | | Ctrl+BracketRight | 」 | Ctrl+] | | Ctrl+」 | ctrl+] | Ctrl+] | ctrl+[BracketRight] | NO | | Shift+BracketRight | 』 | Shift+] | 1 | Shift+」 | shift+] | Shift+] | shift+[BracketRight] | NO | | Ctrl+Shift+BracketRight | 』 | Ctrl+Shift+] | | Ctrl+Shift+」 | ctrl+shift+] | Ctrl+Shift+] | ctrl+shift+[BracketRight] | NO | -| Alt+BracketRight | 」 | Alt+] | | Alt+」 | alt+] | Alt+] | alt+[BracketRight] | NO | -| Ctrl+Alt+BracketRight | ] | ] | 2 | Ctrl+Alt+」 | ctrl+alt+[BracketRight] | Ctrl+Alt+] | ctrl+alt+[BracketRight] | NO | -| Shift+Alt+BracketRight | 』 | Shift+Alt+] | | Shift+Alt+」 | shift+alt+] | Shift+Alt+] | shift+alt+[BracketRight] | NO | -| Ctrl+Shift+Alt+BracketRight | } | Shift+] | 2 | Ctrl+Shift+Alt+」 | ctrl+shift+alt+[BracketRight] | Ctrl+Shift+Alt+] | ctrl+shift+alt+[BracketRight] | NO | +| Alt+BracketRight | 」 | Alt+] | | Option+」 | alt+] | Alt+] | alt+[BracketRight] | NO | +| Ctrl+Alt+BracketRight | ] | ] | 2 | Ctrl+Option+」 | ctrl+alt+[BracketRight] | Ctrl+Alt+] | ctrl+alt+[BracketRight] | NO | +| Shift+Alt+BracketRight | 』 | Shift+Alt+] | | Shift+Option+」 | shift+alt+] | Shift+Alt+] | shift+alt+[BracketRight] | NO | +| Ctrl+Shift+Alt+BracketRight | } | Shift+] | 2 | Ctrl+Shift+Option+」 | ctrl+shift+alt+[BracketRight] | Ctrl+Shift+Alt+] | ctrl+shift+alt+[BracketRight] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -386,10 +386,10 @@ isUSStandard: false | Ctrl+Backslash | 、 | | | Ctrl+、 | ctrl+[Backslash] | null | ctrl+[Backslash] | NO | | Shift+Backslash | | | Shift+\ | | Shift+、 | shift+[Backslash] | null | shift+[Backslash] | NO | | Ctrl+Shift+Backslash | | | Ctrl+Shift+\ | | Ctrl+Shift+、 | ctrl+shift+[Backslash] | null | ctrl+shift+[Backslash] | NO | -| Alt+Backslash | 、 | | | Alt+、 | alt+[Backslash] | null | alt+[Backslash] | NO | -| Ctrl+Alt+Backslash | \ | \ | | Ctrl+Alt+、 | ctrl+alt+[Backslash] | null | ctrl+alt+[Backslash] | NO | -| Shift+Alt+Backslash | | | Shift+Alt+\ | | Shift+Alt+、 | shift+alt+[Backslash] | null | shift+alt+[Backslash] | NO | -| Ctrl+Shift+Alt+Backslash | | | Ctrl+Shift+Alt+\ | | Ctrl+Shift+Alt+、 | ctrl+shift+alt+[Backslash] | null | ctrl+shift+alt+[Backslash] | NO | +| Alt+Backslash | 、 | | | Option+、 | alt+[Backslash] | null | alt+[Backslash] | NO | +| Ctrl+Alt+Backslash | \ | \ | | Ctrl+Option+、 | ctrl+alt+[Backslash] | null | ctrl+alt+[Backslash] | NO | +| Shift+Alt+Backslash | | | Shift+Alt+\ | | Shift+Option+、 | shift+alt+[Backslash] | null | shift+alt+[Backslash] | NO | +| Ctrl+Shift+Alt+Backslash | | | Ctrl+Shift+Alt+\ | | Ctrl+Shift+Option+、 | ctrl+shift+alt+[Backslash] | null | ctrl+shift+alt+[Backslash] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | IntlHash | --- | | | null | null | null | null | | | Ctrl+IntlHash | --- | | | null | null | null | null | | @@ -404,19 +404,19 @@ isUSStandard: false | Ctrl+Semicolon | ㄤ | | | Ctrl+ㄤ | ctrl+[Semicolon] | null | ctrl+[Semicolon] | NO | | Shift+Semicolon | : | Shift+; | | Shift+ㄤ | shift+[Semicolon] | null | shift+[Semicolon] | NO | | Ctrl+Shift+Semicolon | : | Ctrl+Shift+; | | Ctrl+Shift+ㄤ | ctrl+shift+[Semicolon] | null | ctrl+shift+[Semicolon] | NO | -| Alt+Semicolon | ㄤ | | | Alt+ㄤ | alt+[Semicolon] | null | alt+[Semicolon] | NO | -| Ctrl+Alt+Semicolon | ; | ; | | Ctrl+Alt+ㄤ | ctrl+alt+[Semicolon] | null | ctrl+alt+[Semicolon] | NO | -| Shift+Alt+Semicolon | : | Shift+Alt+; | | Shift+Alt+ㄤ | shift+alt+[Semicolon] | null | shift+alt+[Semicolon] | NO | -| Ctrl+Shift+Alt+Semicolon | : | Ctrl+Shift+Alt+; | | Ctrl+Shift+Alt+ㄤ | ctrl+shift+alt+[Semicolon] | null | ctrl+shift+alt+[Semicolon] | NO | +| Alt+Semicolon | ㄤ | | | Option+ㄤ | alt+[Semicolon] | null | alt+[Semicolon] | NO | +| Ctrl+Alt+Semicolon | ; | ; | | Ctrl+Option+ㄤ | ctrl+alt+[Semicolon] | null | ctrl+alt+[Semicolon] | NO | +| Shift+Alt+Semicolon | : | Shift+Alt+; | | Shift+Option+ㄤ | shift+alt+[Semicolon] | null | shift+alt+[Semicolon] | NO | +| Ctrl+Shift+Alt+Semicolon | : | Ctrl+Shift+Alt+; | | Ctrl+Shift+Option+ㄤ | ctrl+shift+alt+[Semicolon] | null | ctrl+shift+alt+[Semicolon] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Quote | ' | ' | | ' | ' | ' | [Quote] | | | Ctrl+Quote | ' | Ctrl+' | | Ctrl+' | ctrl+' | Ctrl+' | ctrl+[Quote] | | | Shift+Quote | " | Shift+' | | Shift+' | shift+' | Shift+' | shift+[Quote] | | | Ctrl+Shift+Quote | " | Ctrl+Shift+' | | Ctrl+Shift+' | ctrl+shift+' | Ctrl+Shift+' | ctrl+shift+[Quote] | | -| Alt+Quote | ' | Alt+' | | Alt+' | alt+' | Alt+' | alt+[Quote] | | -| Ctrl+Alt+Quote | ' | Ctrl+Alt+' | | Ctrl+Alt+' | ctrl+alt+' | Ctrl+Alt+' | ctrl+alt+[Quote] | | -| Shift+Alt+Quote | " | Shift+Alt+' | | Shift+Alt+' | shift+alt+' | Shift+Alt+' | shift+alt+[Quote] | | -| Ctrl+Shift+Alt+Quote | " | Ctrl+Shift+Alt+' | | Ctrl+Shift+Alt+' | ctrl+shift+alt+' | Ctrl+Shift+Alt+' | ctrl+shift+alt+[Quote] | | +| Alt+Quote | ' | Alt+' | | Option+' | alt+' | Alt+' | alt+[Quote] | | +| Ctrl+Alt+Quote | ' | Ctrl+Alt+' | | Ctrl+Option+' | ctrl+alt+' | Ctrl+Alt+' | ctrl+alt+[Quote] | | +| Shift+Alt+Quote | " | Shift+Alt+' | | Shift+Option+' | shift+alt+' | Shift+Alt+' | shift+alt+[Quote] | | +| Ctrl+Shift+Alt+Quote | " | Ctrl+Shift+Alt+' | | Ctrl+Shift+Option+' | ctrl+shift+alt+' | Ctrl+Shift+Alt+' | ctrl+shift+alt+[Quote] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -424,37 +424,37 @@ isUSStandard: false | Ctrl+Backquote | · | | | Ctrl+· | ctrl+[Backquote] | null | ctrl+[Backquote] | NO | | Shift+Backquote | ~ | Shift+` | | Shift+· | shift+[Backquote] | null | shift+[Backquote] | NO | | Ctrl+Shift+Backquote | ~ | Ctrl+Shift+` | | Ctrl+Shift+· | ctrl+shift+[Backquote] | null | ctrl+shift+[Backquote] | NO | -| Alt+Backquote | · | | | Alt+· | alt+[Backquote] | null | alt+[Backquote] | NO | -| Ctrl+Alt+Backquote | ` | ` | | Ctrl+Alt+· | ctrl+alt+[Backquote] | null | ctrl+alt+[Backquote] | NO | -| Shift+Alt+Backquote | ~ | Shift+Alt+` | | Shift+Alt+· | shift+alt+[Backquote] | null | shift+alt+[Backquote] | NO | -| Ctrl+Shift+Alt+Backquote | ~ | Ctrl+Shift+Alt+` | | Ctrl+Shift+Alt+· | ctrl+shift+alt+[Backquote] | null | ctrl+shift+alt+[Backquote] | NO | +| Alt+Backquote | · | | | Option+· | alt+[Backquote] | null | alt+[Backquote] | NO | +| Ctrl+Alt+Backquote | ` | ` | | Ctrl+Option+· | ctrl+alt+[Backquote] | null | ctrl+alt+[Backquote] | NO | +| Shift+Alt+Backquote | ~ | Shift+Alt+` | | Shift+Option+· | shift+alt+[Backquote] | null | shift+alt+[Backquote] | NO | +| Ctrl+Shift+Alt+Backquote | ~ | Ctrl+Shift+Alt+` | | Ctrl+Shift+Option+· | ctrl+shift+alt+[Backquote] | null | ctrl+shift+alt+[Backquote] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Comma | ㄝ | | | ㄝ | [Comma] | null | [Comma] | NO | | Ctrl+Comma | ㄝ | | | Ctrl+ㄝ | ctrl+[Comma] | null | ctrl+[Comma] | NO | | Shift+Comma | , | , | 1 | Shift+ㄝ | shift+[Comma] | null | shift+[Comma] | NO | | Ctrl+Shift+Comma | , | Ctrl+, | | Ctrl+Shift+ㄝ | ctrl+shift+[Comma] | null | ctrl+shift+[Comma] | NO | -| Alt+Comma | ㄝ | | | Alt+ㄝ | alt+[Comma] | null | alt+[Comma] | NO | -| Ctrl+Alt+Comma | , | , | 2 | Ctrl+Alt+ㄝ | ctrl+alt+[Comma] | null | ctrl+alt+[Comma] | NO | -| Shift+Alt+Comma | , | Alt+, | | Shift+Alt+ㄝ | shift+alt+[Comma] | null | shift+alt+[Comma] | NO | -| Ctrl+Shift+Alt+Comma | < | Shift+, | | Ctrl+Shift+Alt+ㄝ | ctrl+shift+alt+[Comma] | null | ctrl+shift+alt+[Comma] | NO | +| Alt+Comma | ㄝ | | | Option+ㄝ | alt+[Comma] | null | alt+[Comma] | NO | +| Ctrl+Alt+Comma | , | , | 2 | Ctrl+Option+ㄝ | ctrl+alt+[Comma] | null | ctrl+alt+[Comma] | NO | +| Shift+Alt+Comma | , | Alt+, | | Shift+Option+ㄝ | shift+alt+[Comma] | null | shift+alt+[Comma] | NO | +| Ctrl+Shift+Alt+Comma | < | Shift+, | | Ctrl+Shift+Option+ㄝ | ctrl+shift+alt+[Comma] | null | ctrl+shift+alt+[Comma] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Period | ㄡ | | | ㄡ | [Period] | null | [Period] | NO | | Ctrl+Period | ㄡ | | | Ctrl+ㄡ | ctrl+[Period] | null | ctrl+[Period] | NO | | Shift+Period | 。 | . | 1 | Shift+ㄡ | shift+[Period] | null | shift+[Period] | NO | | Ctrl+Shift+Period | 。 | Ctrl+. | | Ctrl+Shift+ㄡ | ctrl+shift+[Period] | null | ctrl+shift+[Period] | NO | -| Alt+Period | ㄡ | | | Alt+ㄡ | alt+[Period] | null | alt+[Period] | NO | -| Ctrl+Alt+Period | . | . | 2 | Ctrl+Alt+ㄡ | ctrl+alt+[Period] | null | ctrl+alt+[Period] | NO | -| Shift+Alt+Period | 。 | Alt+. | | Shift+Alt+ㄡ | shift+alt+[Period] | null | shift+alt+[Period] | NO | -| Ctrl+Shift+Alt+Period | > | Shift+. | | Ctrl+Shift+Alt+ㄡ | ctrl+shift+alt+[Period] | null | ctrl+shift+alt+[Period] | NO | +| Alt+Period | ㄡ | | | Option+ㄡ | alt+[Period] | null | alt+[Period] | NO | +| Ctrl+Alt+Period | . | . | 2 | Ctrl+Option+ㄡ | ctrl+alt+[Period] | null | ctrl+alt+[Period] | NO | +| Shift+Alt+Period | 。 | Alt+. | | Shift+Option+ㄡ | shift+alt+[Period] | null | shift+alt+[Period] | NO | +| Ctrl+Shift+Alt+Period | > | Shift+. | | Ctrl+Shift+Option+ㄡ | ctrl+shift+alt+[Period] | null | ctrl+shift+alt+[Period] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Slash | ㄥ | | | ㄥ | [Slash] | null | [Slash] | NO | | Ctrl+Slash | ㄥ | | | Ctrl+ㄥ | ctrl+[Slash] | null | ctrl+[Slash] | NO | | Shift+Slash | ? | Shift+/ | | Shift+ㄥ | shift+[Slash] | null | shift+[Slash] | NO | | Ctrl+Shift+Slash | ? | Ctrl+Shift+/ | | Ctrl+Shift+ㄥ | ctrl+shift+[Slash] | null | ctrl+shift+[Slash] | NO | -| Alt+Slash | ㄥ | | | Alt+ㄥ | alt+[Slash] | null | alt+[Slash] | NO | -| Ctrl+Alt+Slash | / | / | | Ctrl+Alt+ㄥ | ctrl+alt+[Slash] | null | ctrl+alt+[Slash] | NO | -| Shift+Alt+Slash | ? | Shift+Alt+/ | | Shift+Alt+ㄥ | shift+alt+[Slash] | null | shift+alt+[Slash] | NO | -| Ctrl+Shift+Alt+Slash | ? | Ctrl+Shift+Alt+/ | | Ctrl+Shift+Alt+ㄥ | ctrl+shift+alt+[Slash] | null | ctrl+shift+alt+[Slash] | NO | +| Alt+Slash | ㄥ | | | Option+ㄥ | alt+[Slash] | null | alt+[Slash] | NO | +| Ctrl+Alt+Slash | / | / | | Ctrl+Option+ㄥ | ctrl+alt+[Slash] | null | ctrl+alt+[Slash] | NO | +| Shift+Alt+Slash | ? | Shift+Alt+/ | | Shift+Option+ㄥ | shift+alt+[Slash] | null | shift+alt+[Slash] | NO | +| Ctrl+Shift+Alt+Slash | ? | Ctrl+Shift+Alt+/ | | Ctrl+Shift+Option+ㄥ | ctrl+shift+alt+[Slash] | null | ctrl+shift+alt+[Slash] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -462,28 +462,28 @@ isUSStandard: false | Ctrl+ArrowUp | --- | Ctrl+UpArrow | | Ctrl+UpArrow | ctrl+up | Ctrl+Up | ctrl+[ArrowUp] | | | Shift+ArrowUp | --- | Shift+UpArrow | | Shift+UpArrow | shift+up | Shift+Up | shift+[ArrowUp] | | | Ctrl+Shift+ArrowUp | --- | Ctrl+Shift+UpArrow | | Ctrl+Shift+UpArrow | ctrl+shift+up | Ctrl+Shift+Up | ctrl+shift+[ArrowUp] | | -| Alt+ArrowUp | --- | Alt+UpArrow | | Alt+UpArrow | alt+up | Alt+Up | alt+[ArrowUp] | | -| Ctrl+Alt+ArrowUp | --- | Ctrl+Alt+UpArrow | | Ctrl+Alt+UpArrow | ctrl+alt+up | Ctrl+Alt+Up | ctrl+alt+[ArrowUp] | | -| Shift+Alt+ArrowUp | --- | Shift+Alt+UpArrow | | Shift+Alt+UpArrow | shift+alt+up | Shift+Alt+Up | shift+alt+[ArrowUp] | | -| Ctrl+Shift+Alt+ArrowUp | --- | Ctrl+Shift+Alt+UpArrow | | Ctrl+Shift+Alt+UpArrow | ctrl+shift+alt+up | Ctrl+Shift+Alt+Up | ctrl+shift+alt+[ArrowUp] | | +| Alt+ArrowUp | --- | Alt+UpArrow | | Option+UpArrow | alt+up | Alt+Up | alt+[ArrowUp] | | +| Ctrl+Alt+ArrowUp | --- | Ctrl+Alt+UpArrow | | Ctrl+Option+UpArrow | ctrl+alt+up | Ctrl+Alt+Up | ctrl+alt+[ArrowUp] | | +| Shift+Alt+ArrowUp | --- | Shift+Alt+UpArrow | | Shift+Option+UpArrow | shift+alt+up | Shift+Alt+Up | shift+alt+[ArrowUp] | | +| Ctrl+Shift+Alt+ArrowUp | --- | Ctrl+Shift+Alt+UpArrow | | Ctrl+Shift+Option+UpArrow | ctrl+shift+alt+up | Ctrl+Shift+Alt+Up | ctrl+shift+alt+[ArrowUp] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Numpad0 | --- | NumPad0 | | NumPad0 | numpad0 | null | [Numpad0] | | | Ctrl+Numpad0 | --- | Ctrl+NumPad0 | | Ctrl+NumPad0 | ctrl+numpad0 | null | ctrl+[Numpad0] | | | Shift+Numpad0 | --- | Shift+NumPad0 | | Shift+NumPad0 | shift+numpad0 | null | shift+[Numpad0] | | | Ctrl+Shift+Numpad0 | --- | Ctrl+Shift+NumPad0 | | Ctrl+Shift+NumPad0 | ctrl+shift+numpad0 | null | ctrl+shift+[Numpad0] | | -| Alt+Numpad0 | --- | Alt+NumPad0 | | Alt+NumPad0 | alt+numpad0 | null | alt+[Numpad0] | | -| Ctrl+Alt+Numpad0 | --- | Ctrl+Alt+NumPad0 | | Ctrl+Alt+NumPad0 | ctrl+alt+numpad0 | null | ctrl+alt+[Numpad0] | | -| Shift+Alt+Numpad0 | --- | Shift+Alt+NumPad0 | | Shift+Alt+NumPad0 | shift+alt+numpad0 | null | shift+alt+[Numpad0] | | -| Ctrl+Shift+Alt+Numpad0 | --- | Ctrl+Shift+Alt+NumPad0 | | Ctrl+Shift+Alt+NumPad0 | ctrl+shift+alt+numpad0 | null | ctrl+shift+alt+[Numpad0] | | +| Alt+Numpad0 | --- | Alt+NumPad0 | | Option+NumPad0 | alt+numpad0 | null | alt+[Numpad0] | | +| Ctrl+Alt+Numpad0 | --- | Ctrl+Alt+NumPad0 | | Ctrl+Option+NumPad0 | ctrl+alt+numpad0 | null | ctrl+alt+[Numpad0] | | +| Shift+Alt+Numpad0 | --- | Shift+Alt+NumPad0 | | Shift+Option+NumPad0 | shift+alt+numpad0 | null | shift+alt+[Numpad0] | | +| Ctrl+Shift+Alt+Numpad0 | --- | Ctrl+Shift+Alt+NumPad0 | | Ctrl+Shift+Option+NumPad0 | ctrl+shift+alt+numpad0 | null | ctrl+shift+alt+[Numpad0] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | IntlBackslash | § | | | § | [IntlBackslash] | null | [IntlBackslash] | NO | | Ctrl+IntlBackslash | § | | | Ctrl+§ | ctrl+[IntlBackslash] | null | ctrl+[IntlBackslash] | NO | | Shift+IntlBackslash | ± | | | Shift+§ | shift+[IntlBackslash] | null | shift+[IntlBackslash] | NO | | Ctrl+Shift+IntlBackslash | ± | | | Ctrl+Shift+§ | ctrl+shift+[IntlBackslash] | null | ctrl+shift+[IntlBackslash] | NO | -| Alt+IntlBackslash | § | | | Alt+§ | alt+[IntlBackslash] | null | alt+[IntlBackslash] | NO | -| Ctrl+Alt+IntlBackslash | --- | | | Ctrl+Alt+§ | ctrl+alt+[IntlBackslash] | null | ctrl+alt+[IntlBackslash] | NO | -| Shift+Alt+IntlBackslash | ± | | | Shift+Alt+§ | shift+alt+[IntlBackslash] | null | shift+alt+[IntlBackslash] | NO | -| Ctrl+Shift+Alt+IntlBackslash | --- | | | Ctrl+Shift+Alt+§ | ctrl+shift+alt+[IntlBackslash] | null | ctrl+shift+alt+[IntlBackslash] | NO | +| Alt+IntlBackslash | § | | | Option+§ | alt+[IntlBackslash] | null | alt+[IntlBackslash] | NO | +| Ctrl+Alt+IntlBackslash | --- | | | Ctrl+Option+§ | ctrl+alt+[IntlBackslash] | null | ctrl+alt+[IntlBackslash] | NO | +| Shift+Alt+IntlBackslash | ± | | | Shift+Option+§ | shift+alt+[IntlBackslash] | null | shift+alt+[IntlBackslash] | NO | +| Ctrl+Shift+Alt+IntlBackslash | --- | | | Ctrl+Shift+Option+§ | ctrl+shift+alt+[IntlBackslash] | null | ctrl+shift+alt+[IntlBackslash] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | IntlRo | --- | | | null | [IntlRo] | null | [IntlRo] | NO | | Ctrl+IntlRo | --- | | | null | ctrl+[IntlRo] | null | ctrl+[IntlRo] | NO | diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/mac_zh_hant2.txt b/src/vs/workbench/services/keybinding/test/electron-browser/mac_zh_hant2.txt index a9fe14f79eb..17b24363b35 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/mac_zh_hant2.txt +++ b/src/vs/workbench/services/keybinding/test/electron-browser/mac_zh_hant2.txt @@ -6,37 +6,37 @@ isUSStandard: false | Ctrl+KeyA | a | Ctrl+A | | Ctrl+A | ctrl+a | Ctrl+A | ctrl+[KeyA] | | | Shift+KeyA | A | Shift+A | | Shift+A | shift+a | Shift+A | shift+[KeyA] | | | Ctrl+Shift+KeyA | A | Ctrl+Shift+A | | Ctrl+Shift+A | ctrl+shift+a | Ctrl+Shift+A | ctrl+shift+[KeyA] | | -| Alt+KeyA | a | Alt+A | | Alt+A | alt+a | Alt+A | alt+[KeyA] | | -| Ctrl+Alt+KeyA | å | Ctrl+Alt+A | | Ctrl+Alt+A | ctrl+alt+a | Ctrl+Alt+A | ctrl+alt+[KeyA] | | -| Shift+Alt+KeyA | A | Shift+Alt+A | | Shift+Alt+A | shift+alt+a | Shift+Alt+A | shift+alt+[KeyA] | | -| Ctrl+Shift+Alt+KeyA | Å | Ctrl+Shift+Alt+A | | Ctrl+Shift+Alt+A | ctrl+shift+alt+a | Ctrl+Shift+Alt+A | ctrl+shift+alt+[KeyA] | | +| Alt+KeyA | a | Alt+A | | Option+A | alt+a | Alt+A | alt+[KeyA] | | +| Ctrl+Alt+KeyA | å | Ctrl+Alt+A | | Ctrl+Option+A | ctrl+alt+a | Ctrl+Alt+A | ctrl+alt+[KeyA] | | +| Shift+Alt+KeyA | A | Shift+Alt+A | | Shift+Option+A | shift+alt+a | Shift+Alt+A | shift+alt+[KeyA] | | +| Ctrl+Shift+Alt+KeyA | Å | Ctrl+Shift+Alt+A | | Ctrl+Shift+Option+A | ctrl+shift+alt+a | Ctrl+Shift+Alt+A | ctrl+shift+alt+[KeyA] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyB | b | B | | B | b | B | [KeyB] | | | Ctrl+KeyB | b | Ctrl+B | | Ctrl+B | ctrl+b | Ctrl+B | ctrl+[KeyB] | | | Shift+KeyB | B | Shift+B | | Shift+B | shift+b | Shift+B | shift+[KeyB] | | | Ctrl+Shift+KeyB | B | Ctrl+Shift+B | | Ctrl+Shift+B | ctrl+shift+b | Ctrl+Shift+B | ctrl+shift+[KeyB] | | -| Alt+KeyB | b | Alt+B | | Alt+B | alt+b | Alt+B | alt+[KeyB] | | -| Ctrl+Alt+KeyB | ∫ | Ctrl+Alt+B | | Ctrl+Alt+B | ctrl+alt+b | Ctrl+Alt+B | ctrl+alt+[KeyB] | | -| Shift+Alt+KeyB | B | Shift+Alt+B | | Shift+Alt+B | shift+alt+b | Shift+Alt+B | shift+alt+[KeyB] | | -| Ctrl+Shift+Alt+KeyB | 符 | Ctrl+Shift+Alt+B | | Ctrl+Shift+Alt+B | ctrl+shift+alt+b | Ctrl+Shift+Alt+B | ctrl+shift+alt+[KeyB] | | +| Alt+KeyB | b | Alt+B | | Option+B | alt+b | Alt+B | alt+[KeyB] | | +| Ctrl+Alt+KeyB | ∫ | Ctrl+Alt+B | | Ctrl+Option+B | ctrl+alt+b | Ctrl+Alt+B | ctrl+alt+[KeyB] | | +| Shift+Alt+KeyB | B | Shift+Alt+B | | Shift+Option+B | shift+alt+b | Shift+Alt+B | shift+alt+[KeyB] | | +| Ctrl+Shift+Alt+KeyB | 符 | Ctrl+Shift+Alt+B | | Ctrl+Shift+Option+B | ctrl+shift+alt+b | Ctrl+Shift+Alt+B | ctrl+shift+alt+[KeyB] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyC | c | C | | C | c | C | [KeyC] | | | Ctrl+KeyC | c | Ctrl+C | | Ctrl+C | ctrl+c | Ctrl+C | ctrl+[KeyC] | | | Shift+KeyC | C | Shift+C | | Shift+C | shift+c | Shift+C | shift+[KeyC] | | | Ctrl+Shift+KeyC | C | Ctrl+Shift+C | | Ctrl+Shift+C | ctrl+shift+c | Ctrl+Shift+C | ctrl+shift+[KeyC] | | -| Alt+KeyC | c | Alt+C | | Alt+C | alt+c | Alt+C | alt+[KeyC] | | -| Ctrl+Alt+KeyC | ç | Ctrl+Alt+C | | Ctrl+Alt+C | ctrl+alt+c | Ctrl+Alt+C | ctrl+alt+[KeyC] | | -| Shift+Alt+KeyC | C | Shift+Alt+C | | Shift+Alt+C | shift+alt+c | Shift+Alt+C | shift+alt+[KeyC] | | -| Ctrl+Shift+Alt+KeyC | Ç | Ctrl+Shift+Alt+C | | Ctrl+Shift+Alt+C | ctrl+shift+alt+c | Ctrl+Shift+Alt+C | ctrl+shift+alt+[KeyC] | | +| Alt+KeyC | c | Alt+C | | Option+C | alt+c | Alt+C | alt+[KeyC] | | +| Ctrl+Alt+KeyC | ç | Ctrl+Alt+C | | Ctrl+Option+C | ctrl+alt+c | Ctrl+Alt+C | ctrl+alt+[KeyC] | | +| Shift+Alt+KeyC | C | Shift+Alt+C | | Shift+Option+C | shift+alt+c | Shift+Alt+C | shift+alt+[KeyC] | | +| Ctrl+Shift+Alt+KeyC | Ç | Ctrl+Shift+Alt+C | | Ctrl+Shift+Option+C | ctrl+shift+alt+c | Ctrl+Shift+Alt+C | ctrl+shift+alt+[KeyC] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyD | d | D | | D | d | D | [KeyD] | | | Ctrl+KeyD | d | Ctrl+D | | Ctrl+D | ctrl+d | Ctrl+D | ctrl+[KeyD] | | | Shift+KeyD | D | Shift+D | | Shift+D | shift+d | Shift+D | shift+[KeyD] | | | Ctrl+Shift+KeyD | D | Ctrl+Shift+D | | Ctrl+Shift+D | ctrl+shift+d | Ctrl+Shift+D | ctrl+shift+[KeyD] | | -| Alt+KeyD | d | Alt+D | | Alt+D | alt+d | Alt+D | alt+[KeyD] | | -| Ctrl+Alt+KeyD | ∂ | Ctrl+Alt+D | | Ctrl+Alt+D | ctrl+alt+d | Ctrl+Alt+D | ctrl+alt+[KeyD] | | -| Shift+Alt+KeyD | D | Shift+Alt+D | | Shift+Alt+D | shift+alt+d | Shift+Alt+D | shift+alt+[KeyD] | | -| Ctrl+Shift+Alt+KeyD | Î | Ctrl+Shift+Alt+D | | Ctrl+Shift+Alt+D | ctrl+shift+alt+d | Ctrl+Shift+Alt+D | ctrl+shift+alt+[KeyD] | | +| Alt+KeyD | d | Alt+D | | Option+D | alt+d | Alt+D | alt+[KeyD] | | +| Ctrl+Alt+KeyD | ∂ | Ctrl+Alt+D | | Ctrl+Option+D | ctrl+alt+d | Ctrl+Alt+D | ctrl+alt+[KeyD] | | +| Shift+Alt+KeyD | D | Shift+Alt+D | | Shift+Option+D | shift+alt+d | Shift+Alt+D | shift+alt+[KeyD] | | +| Ctrl+Shift+Alt+KeyD | Î | Ctrl+Shift+Alt+D | | Ctrl+Shift+Option+D | ctrl+shift+alt+d | Ctrl+Shift+Alt+D | ctrl+shift+alt+[KeyD] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -44,37 +44,37 @@ isUSStandard: false | Ctrl+KeyE | e | Ctrl+E | | Ctrl+E | ctrl+e | Ctrl+E | ctrl+[KeyE] | | | Shift+KeyE | E | Shift+E | | Shift+E | shift+e | Shift+E | shift+[KeyE] | | | Ctrl+Shift+KeyE | E | Ctrl+Shift+E | | Ctrl+Shift+E | ctrl+shift+e | Ctrl+Shift+E | ctrl+shift+[KeyE] | | -| Alt+KeyE | e | Alt+E | | Alt+E | alt+e | Alt+E | alt+[KeyE] | | -| Ctrl+Alt+KeyE | ´ | Ctrl+Alt+E | | Ctrl+Alt+E | ctrl+alt+e | Ctrl+Alt+E | ctrl+alt+[KeyE] | | -| Shift+Alt+KeyE | E | Shift+Alt+E | | Shift+Alt+E | shift+alt+e | Shift+Alt+E | shift+alt+[KeyE] | | -| Ctrl+Shift+Alt+KeyE | 助 | Ctrl+Shift+Alt+E | | Ctrl+Shift+Alt+E | ctrl+shift+alt+e | Ctrl+Shift+Alt+E | ctrl+shift+alt+[KeyE] | | +| Alt+KeyE | e | Alt+E | | Option+E | alt+e | Alt+E | alt+[KeyE] | | +| Ctrl+Alt+KeyE | ´ | Ctrl+Alt+E | | Ctrl+Option+E | ctrl+alt+e | Ctrl+Alt+E | ctrl+alt+[KeyE] | | +| Shift+Alt+KeyE | E | Shift+Alt+E | | Shift+Option+E | shift+alt+e | Shift+Alt+E | shift+alt+[KeyE] | | +| Ctrl+Shift+Alt+KeyE | 助 | Ctrl+Shift+Alt+E | | Ctrl+Shift+Option+E | ctrl+shift+alt+e | Ctrl+Shift+Alt+E | ctrl+shift+alt+[KeyE] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyF | f | F | | F | f | F | [KeyF] | | | Ctrl+KeyF | f | Ctrl+F | | Ctrl+F | ctrl+f | Ctrl+F | ctrl+[KeyF] | | | Shift+KeyF | F | Shift+F | | Shift+F | shift+f | Shift+F | shift+[KeyF] | | | Ctrl+Shift+KeyF | F | Ctrl+Shift+F | | Ctrl+Shift+F | ctrl+shift+f | Ctrl+Shift+F | ctrl+shift+[KeyF] | | -| Alt+KeyF | f | Alt+F | | Alt+F | alt+f | Alt+F | alt+[KeyF] | | -| Ctrl+Alt+KeyF | ƒ | Ctrl+Alt+F | | Ctrl+Alt+F | ctrl+alt+f | Ctrl+Alt+F | ctrl+alt+[KeyF] | | -| Shift+Alt+KeyF | F | Shift+Alt+F | | Shift+Alt+F | shift+alt+f | Shift+Alt+F | shift+alt+[KeyF] | | -| Ctrl+Shift+Alt+KeyF | Ï | Ctrl+Shift+Alt+F | | Ctrl+Shift+Alt+F | ctrl+shift+alt+f | Ctrl+Shift+Alt+F | ctrl+shift+alt+[KeyF] | | +| Alt+KeyF | f | Alt+F | | Option+F | alt+f | Alt+F | alt+[KeyF] | | +| Ctrl+Alt+KeyF | ƒ | Ctrl+Alt+F | | Ctrl+Option+F | ctrl+alt+f | Ctrl+Alt+F | ctrl+alt+[KeyF] | | +| Shift+Alt+KeyF | F | Shift+Alt+F | | Shift+Option+F | shift+alt+f | Shift+Alt+F | shift+alt+[KeyF] | | +| Ctrl+Shift+Alt+KeyF | Ï | Ctrl+Shift+Alt+F | | Ctrl+Shift+Option+F | ctrl+shift+alt+f | Ctrl+Shift+Alt+F | ctrl+shift+alt+[KeyF] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyG | g | G | | G | g | G | [KeyG] | | | Ctrl+KeyG | g | Ctrl+G | | Ctrl+G | ctrl+g | Ctrl+G | ctrl+[KeyG] | | | Shift+KeyG | G | Shift+G | | Shift+G | shift+g | Shift+G | shift+[KeyG] | | | Ctrl+Shift+KeyG | G | Ctrl+Shift+G | | Ctrl+Shift+G | ctrl+shift+g | Ctrl+Shift+G | ctrl+shift+[KeyG] | | -| Alt+KeyG | g | Alt+G | | Alt+G | alt+g | Alt+G | alt+[KeyG] | | -| Ctrl+Alt+KeyG | © | Ctrl+Alt+G | | Ctrl+Alt+G | ctrl+alt+g | Ctrl+Alt+G | ctrl+alt+[KeyG] | | -| Shift+Alt+KeyG | G | Shift+Alt+G | | Shift+Alt+G | shift+alt+g | Shift+Alt+G | shift+alt+[KeyG] | | -| Ctrl+Shift+Alt+KeyG | ˝ | Ctrl+Shift+Alt+G | | Ctrl+Shift+Alt+G | ctrl+shift+alt+g | Ctrl+Shift+Alt+G | ctrl+shift+alt+[KeyG] | | +| Alt+KeyG | g | Alt+G | | Option+G | alt+g | Alt+G | alt+[KeyG] | | +| Ctrl+Alt+KeyG | © | Ctrl+Alt+G | | Ctrl+Option+G | ctrl+alt+g | Ctrl+Alt+G | ctrl+alt+[KeyG] | | +| Shift+Alt+KeyG | G | Shift+Alt+G | | Shift+Option+G | shift+alt+g | Shift+Alt+G | shift+alt+[KeyG] | | +| Ctrl+Shift+Alt+KeyG | ˝ | Ctrl+Shift+Alt+G | | Ctrl+Shift+Option+G | ctrl+shift+alt+g | Ctrl+Shift+Alt+G | ctrl+shift+alt+[KeyG] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyH | h | H | | H | h | H | [KeyH] | | | Ctrl+KeyH | h | Ctrl+H | | Ctrl+H | ctrl+h | Ctrl+H | ctrl+[KeyH] | | | Shift+KeyH | H | Shift+H | | Shift+H | shift+h | Shift+H | shift+[KeyH] | | | Ctrl+Shift+KeyH | H | Ctrl+Shift+H | | Ctrl+Shift+H | ctrl+shift+h | Ctrl+Shift+H | ctrl+shift+[KeyH] | | -| Alt+KeyH | h | Alt+H | | Alt+H | alt+h | Alt+H | alt+[KeyH] | | -| Ctrl+Alt+KeyH | ˙ | Ctrl+Alt+H | | Ctrl+Alt+H | ctrl+alt+h | Ctrl+Alt+H | ctrl+alt+[KeyH] | | -| Shift+Alt+KeyH | H | Shift+Alt+H | | Shift+Alt+H | shift+alt+h | Shift+Alt+H | shift+alt+[KeyH] | | -| Ctrl+Shift+Alt+KeyH | 半 | Ctrl+Shift+Alt+H | | Ctrl+Shift+Alt+H | ctrl+shift+alt+h | Ctrl+Shift+Alt+H | ctrl+shift+alt+[KeyH] | | +| Alt+KeyH | h | Alt+H | | Option+H | alt+h | Alt+H | alt+[KeyH] | | +| Ctrl+Alt+KeyH | ˙ | Ctrl+Alt+H | | Ctrl+Option+H | ctrl+alt+h | Ctrl+Alt+H | ctrl+alt+[KeyH] | | +| Shift+Alt+KeyH | H | Shift+Alt+H | | Shift+Option+H | shift+alt+h | Shift+Alt+H | shift+alt+[KeyH] | | +| Ctrl+Shift+Alt+KeyH | 半 | Ctrl+Shift+Alt+H | | Ctrl+Shift+Option+H | ctrl+shift+alt+h | Ctrl+Shift+Alt+H | ctrl+shift+alt+[KeyH] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -82,37 +82,37 @@ isUSStandard: false | Ctrl+KeyI | i | Ctrl+I | | Ctrl+I | ctrl+i | Ctrl+I | ctrl+[KeyI] | | | Shift+KeyI | I | Shift+I | | Shift+I | shift+i | Shift+I | shift+[KeyI] | | | Ctrl+Shift+KeyI | I | Ctrl+Shift+I | | Ctrl+Shift+I | ctrl+shift+i | Ctrl+Shift+I | ctrl+shift+[KeyI] | | -| Alt+KeyI | i | Alt+I | | Alt+I | alt+i | Alt+I | alt+[KeyI] | | -| Ctrl+Alt+KeyI | ˆ | Ctrl+Alt+I | | Ctrl+Alt+I | ctrl+alt+i | Ctrl+Alt+I | ctrl+alt+[KeyI] | | -| Shift+Alt+KeyI | I | Shift+Alt+I | | Shift+Alt+I | shift+alt+i | Shift+Alt+I | shift+alt+[KeyI] | | -| Ctrl+Shift+Alt+KeyI | ˆ | Ctrl+Shift+Alt+I | | Ctrl+Shift+Alt+I | ctrl+shift+alt+i | Ctrl+Shift+Alt+I | ctrl+shift+alt+[KeyI] | | +| Alt+KeyI | i | Alt+I | | Option+I | alt+i | Alt+I | alt+[KeyI] | | +| Ctrl+Alt+KeyI | ˆ | Ctrl+Alt+I | | Ctrl+Option+I | ctrl+alt+i | Ctrl+Alt+I | ctrl+alt+[KeyI] | | +| Shift+Alt+KeyI | I | Shift+Alt+I | | Shift+Option+I | shift+alt+i | Shift+Alt+I | shift+alt+[KeyI] | | +| Ctrl+Shift+Alt+KeyI | ˆ | Ctrl+Shift+Alt+I | | Ctrl+Shift+Option+I | ctrl+shift+alt+i | Ctrl+Shift+Alt+I | ctrl+shift+alt+[KeyI] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyJ | j | J | | J | j | J | [KeyJ] | | | Ctrl+KeyJ | j | Ctrl+J | | Ctrl+J | ctrl+j | Ctrl+J | ctrl+[KeyJ] | | | Shift+KeyJ | J | Shift+J | | Shift+J | shift+j | Shift+J | shift+[KeyJ] | | | Ctrl+Shift+KeyJ | J | Ctrl+Shift+J | | Ctrl+Shift+J | ctrl+shift+j | Ctrl+Shift+J | ctrl+shift+[KeyJ] | | -| Alt+KeyJ | j | Alt+J | | Alt+J | alt+j | Alt+J | alt+[KeyJ] | | -| Ctrl+Alt+KeyJ | ∆ | Ctrl+Alt+J | | Ctrl+Alt+J | ctrl+alt+j | Ctrl+Alt+J | ctrl+alt+[KeyJ] | | -| Shift+Alt+KeyJ | J | Shift+Alt+J | | Shift+Alt+J | shift+alt+j | Shift+Alt+J | shift+alt+[KeyJ] | | -| Ctrl+Shift+Alt+KeyJ | Ô | Ctrl+Shift+Alt+J | | Ctrl+Shift+Alt+J | ctrl+shift+alt+j | Ctrl+Shift+Alt+J | ctrl+shift+alt+[KeyJ] | | +| Alt+KeyJ | j | Alt+J | | Option+J | alt+j | Alt+J | alt+[KeyJ] | | +| Ctrl+Alt+KeyJ | ∆ | Ctrl+Alt+J | | Ctrl+Option+J | ctrl+alt+j | Ctrl+Alt+J | ctrl+alt+[KeyJ] | | +| Shift+Alt+KeyJ | J | Shift+Alt+J | | Shift+Option+J | shift+alt+j | Shift+Alt+J | shift+alt+[KeyJ] | | +| Ctrl+Shift+Alt+KeyJ | Ô | Ctrl+Shift+Alt+J | | Ctrl+Shift+Option+J | ctrl+shift+alt+j | Ctrl+Shift+Alt+J | ctrl+shift+alt+[KeyJ] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyK | k | K | | K | k | K | [KeyK] | | | Ctrl+KeyK | k | Ctrl+K | | Ctrl+K | ctrl+k | Ctrl+K | ctrl+[KeyK] | | | Shift+KeyK | K | Shift+K | | Shift+K | shift+k | Shift+K | shift+[KeyK] | | | Ctrl+Shift+KeyK | K | Ctrl+Shift+K | | Ctrl+Shift+K | ctrl+shift+k | Ctrl+Shift+K | ctrl+shift+[KeyK] | | -| Alt+KeyK | k | Alt+K | | Alt+K | alt+k | Alt+K | alt+[KeyK] | | -| Ctrl+Alt+KeyK | ˚ | Ctrl+Alt+K | | Ctrl+Alt+K | ctrl+alt+k | Ctrl+Alt+K | ctrl+alt+[KeyK] | | -| Shift+Alt+KeyK | K | Shift+Alt+K | | Shift+Alt+K | shift+alt+k | Shift+Alt+K | shift+alt+[KeyK] | | -| Ctrl+Shift+Alt+KeyK |  | Ctrl+Shift+Alt+K | | Ctrl+Shift+Alt+K | ctrl+shift+alt+k | Ctrl+Shift+Alt+K | ctrl+shift+alt+[KeyK] | | +| Alt+KeyK | k | Alt+K | | Option+K | alt+k | Alt+K | alt+[KeyK] | | +| Ctrl+Alt+KeyK | ˚ | Ctrl+Alt+K | | Ctrl+Option+K | ctrl+alt+k | Ctrl+Alt+K | ctrl+alt+[KeyK] | | +| Shift+Alt+KeyK | K | Shift+Alt+K | | Shift+Option+K | shift+alt+k | Shift+Alt+K | shift+alt+[KeyK] | | +| Ctrl+Shift+Alt+KeyK |  | Ctrl+Shift+Alt+K | | Ctrl+Shift+Option+K | ctrl+shift+alt+k | Ctrl+Shift+Alt+K | ctrl+shift+alt+[KeyK] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyL | l | L | | L | l | L | [KeyL] | | | Ctrl+KeyL | l | Ctrl+L | | Ctrl+L | ctrl+l | Ctrl+L | ctrl+[KeyL] | | | Shift+KeyL | L | Shift+L | | Shift+L | shift+l | Shift+L | shift+[KeyL] | | | Ctrl+Shift+KeyL | L | Ctrl+Shift+L | | Ctrl+Shift+L | ctrl+shift+l | Ctrl+Shift+L | ctrl+shift+[KeyL] | | -| Alt+KeyL | l | Alt+L | | Alt+L | alt+l | Alt+L | alt+[KeyL] | | -| Ctrl+Alt+KeyL | ¬ | Ctrl+Alt+L | | Ctrl+Alt+L | ctrl+alt+l | Ctrl+Alt+L | ctrl+alt+[KeyL] | | -| Shift+Alt+KeyL | L | Shift+Alt+L | | Shift+Alt+L | shift+alt+l | Shift+Alt+L | shift+alt+[KeyL] | | -| Ctrl+Shift+Alt+KeyL | 查 | Ctrl+Shift+Alt+L | | Ctrl+Shift+Alt+L | ctrl+shift+alt+l | Ctrl+Shift+Alt+L | ctrl+shift+alt+[KeyL] | | +| Alt+KeyL | l | Alt+L | | Option+L | alt+l | Alt+L | alt+[KeyL] | | +| Ctrl+Alt+KeyL | ¬ | Ctrl+Alt+L | | Ctrl+Option+L | ctrl+alt+l | Ctrl+Alt+L | ctrl+alt+[KeyL] | | +| Shift+Alt+KeyL | L | Shift+Alt+L | | Shift+Option+L | shift+alt+l | Shift+Alt+L | shift+alt+[KeyL] | | +| Ctrl+Shift+Alt+KeyL | 查 | Ctrl+Shift+Alt+L | | Ctrl+Shift+Option+L | ctrl+shift+alt+l | Ctrl+Shift+Alt+L | ctrl+shift+alt+[KeyL] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -120,37 +120,37 @@ isUSStandard: false | Ctrl+KeyM | m | Ctrl+M | | Ctrl+M | ctrl+m | Ctrl+M | ctrl+[KeyM] | | | Shift+KeyM | M | Shift+M | | Shift+M | shift+m | Shift+M | shift+[KeyM] | | | Ctrl+Shift+KeyM | M | Ctrl+Shift+M | | Ctrl+Shift+M | ctrl+shift+m | Ctrl+Shift+M | ctrl+shift+[KeyM] | | -| Alt+KeyM | m | Alt+M | | Alt+M | alt+m | Alt+M | alt+[KeyM] | | -| Ctrl+Alt+KeyM | µ | Ctrl+Alt+M | | Ctrl+Alt+M | ctrl+alt+m | Ctrl+Alt+M | ctrl+alt+[KeyM] | | -| Shift+Alt+KeyM | M | Shift+Alt+M | | Shift+Alt+M | shift+alt+m | Shift+Alt+M | shift+alt+[KeyM] | | -| Ctrl+Shift+Alt+KeyM |  | Ctrl+Shift+Alt+M | | Ctrl+Shift+Alt+M | ctrl+shift+alt+m | Ctrl+Shift+Alt+M | ctrl+shift+alt+[KeyM] | | +| Alt+KeyM | m | Alt+M | | Option+M | alt+m | Alt+M | alt+[KeyM] | | +| Ctrl+Alt+KeyM | µ | Ctrl+Alt+M | | Ctrl+Option+M | ctrl+alt+m | Ctrl+Alt+M | ctrl+alt+[KeyM] | | +| Shift+Alt+KeyM | M | Shift+Alt+M | | Shift+Option+M | shift+alt+m | Shift+Alt+M | shift+alt+[KeyM] | | +| Ctrl+Shift+Alt+KeyM |  | Ctrl+Shift+Alt+M | | Ctrl+Shift+Option+M | ctrl+shift+alt+m | Ctrl+Shift+Alt+M | ctrl+shift+alt+[KeyM] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyN | n | N | | N | n | N | [KeyN] | | | Ctrl+KeyN | n | Ctrl+N | | Ctrl+N | ctrl+n | Ctrl+N | ctrl+[KeyN] | | | Shift+KeyN | N | Shift+N | | Shift+N | shift+n | Shift+N | shift+[KeyN] | | | Ctrl+Shift+KeyN | N | Ctrl+Shift+N | | Ctrl+Shift+N | ctrl+shift+n | Ctrl+Shift+N | ctrl+shift+[KeyN] | | -| Alt+KeyN | n | Alt+N | | Alt+N | alt+n | Alt+N | alt+[KeyN] | | -| Ctrl+Alt+KeyN | ˜ | Ctrl+Alt+N | | Ctrl+Alt+N | ctrl+alt+n | Ctrl+Alt+N | ctrl+alt+[KeyN] | | -| Shift+Alt+KeyN | N | Shift+Alt+N | | Shift+Alt+N | shift+alt+n | Shift+Alt+N | shift+alt+[KeyN] | | -| Ctrl+Shift+Alt+KeyN | ˜ | Ctrl+Shift+Alt+N | | Ctrl+Shift+Alt+N | ctrl+shift+alt+n | Ctrl+Shift+Alt+N | ctrl+shift+alt+[KeyN] | | +| Alt+KeyN | n | Alt+N | | Option+N | alt+n | Alt+N | alt+[KeyN] | | +| Ctrl+Alt+KeyN | ˜ | Ctrl+Alt+N | | Ctrl+Option+N | ctrl+alt+n | Ctrl+Alt+N | ctrl+alt+[KeyN] | | +| Shift+Alt+KeyN | N | Shift+Alt+N | | Shift+Option+N | shift+alt+n | Shift+Alt+N | shift+alt+[KeyN] | | +| Ctrl+Shift+Alt+KeyN | ˜ | Ctrl+Shift+Alt+N | | Ctrl+Shift+Option+N | ctrl+shift+alt+n | Ctrl+Shift+Alt+N | ctrl+shift+alt+[KeyN] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyO | o | O | | O | o | O | [KeyO] | | | Ctrl+KeyO | o | Ctrl+O | | Ctrl+O | ctrl+o | Ctrl+O | ctrl+[KeyO] | | | Shift+KeyO | O | Shift+O | | Shift+O | shift+o | Shift+O | shift+[KeyO] | | | Ctrl+Shift+KeyO | O | Ctrl+Shift+O | | Ctrl+Shift+O | ctrl+shift+o | Ctrl+Shift+O | ctrl+shift+[KeyO] | | -| Alt+KeyO | o | Alt+O | | Alt+O | alt+o | Alt+O | alt+[KeyO] | | -| Ctrl+Alt+KeyO | ø | Ctrl+Alt+O | | Ctrl+Alt+O | ctrl+alt+o | Ctrl+Alt+O | ctrl+alt+[KeyO] | | -| Shift+Alt+KeyO | O | Shift+Alt+O | | Shift+Alt+O | shift+alt+o | Shift+Alt+O | shift+alt+[KeyO] | | -| Ctrl+Shift+Alt+KeyO | Ø | Ctrl+Shift+Alt+O | | Ctrl+Shift+Alt+O | ctrl+shift+alt+o | Ctrl+Shift+Alt+O | ctrl+shift+alt+[KeyO] | | +| Alt+KeyO | o | Alt+O | | Option+O | alt+o | Alt+O | alt+[KeyO] | | +| Ctrl+Alt+KeyO | ø | Ctrl+Alt+O | | Ctrl+Option+O | ctrl+alt+o | Ctrl+Alt+O | ctrl+alt+[KeyO] | | +| Shift+Alt+KeyO | O | Shift+Alt+O | | Shift+Option+O | shift+alt+o | Shift+Alt+O | shift+alt+[KeyO] | | +| Ctrl+Shift+Alt+KeyO | Ø | Ctrl+Shift+Alt+O | | Ctrl+Shift+Option+O | ctrl+shift+alt+o | Ctrl+Shift+Alt+O | ctrl+shift+alt+[KeyO] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyP | p | P | | P | p | P | [KeyP] | | | Ctrl+KeyP | p | Ctrl+P | | Ctrl+P | ctrl+p | Ctrl+P | ctrl+[KeyP] | | | Shift+KeyP | P | Shift+P | | Shift+P | shift+p | Shift+P | shift+[KeyP] | | | Ctrl+Shift+KeyP | P | Ctrl+Shift+P | | Ctrl+Shift+P | ctrl+shift+p | Ctrl+Shift+P | ctrl+shift+[KeyP] | | -| Alt+KeyP | p | Alt+P | | Alt+P | alt+p | Alt+P | alt+[KeyP] | | -| Ctrl+Alt+KeyP | π | Ctrl+Alt+P | | Ctrl+Alt+P | ctrl+alt+p | Ctrl+Alt+P | ctrl+alt+[KeyP] | | -| Shift+Alt+KeyP | P | Shift+Alt+P | | Shift+Alt+P | shift+alt+p | Shift+Alt+P | shift+alt+[KeyP] | | -| Ctrl+Shift+Alt+KeyP | ∏ | Ctrl+Shift+Alt+P | | Ctrl+Shift+Alt+P | ctrl+shift+alt+p | Ctrl+Shift+Alt+P | ctrl+shift+alt+[KeyP] | | +| Alt+KeyP | p | Alt+P | | Option+P | alt+p | Alt+P | alt+[KeyP] | | +| Ctrl+Alt+KeyP | π | Ctrl+Alt+P | | Ctrl+Option+P | ctrl+alt+p | Ctrl+Alt+P | ctrl+alt+[KeyP] | | +| Shift+Alt+KeyP | P | Shift+Alt+P | | Shift+Option+P | shift+alt+p | Shift+Alt+P | shift+alt+[KeyP] | | +| Ctrl+Shift+Alt+KeyP | ∏ | Ctrl+Shift+Alt+P | | Ctrl+Shift+Option+P | ctrl+shift+alt+p | Ctrl+Shift+Alt+P | ctrl+shift+alt+[KeyP] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -158,37 +158,37 @@ isUSStandard: false | Ctrl+KeyQ | q | Ctrl+Q | | Ctrl+Q | ctrl+q | Ctrl+Q | ctrl+[KeyQ] | | | Shift+KeyQ | Q | Shift+Q | | Shift+Q | shift+q | Shift+Q | shift+[KeyQ] | | | Ctrl+Shift+KeyQ | Q | Ctrl+Shift+Q | | Ctrl+Shift+Q | ctrl+shift+q | Ctrl+Shift+Q | ctrl+shift+[KeyQ] | | -| Alt+KeyQ | q | Alt+Q | | Alt+Q | alt+q | Alt+Q | alt+[KeyQ] | | -| Ctrl+Alt+KeyQ | œ | Ctrl+Alt+Q | | Ctrl+Alt+Q | ctrl+alt+q | Ctrl+Alt+Q | ctrl+alt+[KeyQ] | | -| Shift+Alt+KeyQ | Q | Shift+Alt+Q | | Shift+Alt+Q | shift+alt+q | Shift+Alt+Q | shift+alt+[KeyQ] | | -| Ctrl+Shift+Alt+KeyQ | Œ | Ctrl+Shift+Alt+Q | | Ctrl+Shift+Alt+Q | ctrl+shift+alt+q | Ctrl+Shift+Alt+Q | ctrl+shift+alt+[KeyQ] | | +| Alt+KeyQ | q | Alt+Q | | Option+Q | alt+q | Alt+Q | alt+[KeyQ] | | +| Ctrl+Alt+KeyQ | œ | Ctrl+Alt+Q | | Ctrl+Option+Q | ctrl+alt+q | Ctrl+Alt+Q | ctrl+alt+[KeyQ] | | +| Shift+Alt+KeyQ | Q | Shift+Alt+Q | | Shift+Option+Q | shift+alt+q | Shift+Alt+Q | shift+alt+[KeyQ] | | +| Ctrl+Shift+Alt+KeyQ | Œ | Ctrl+Shift+Alt+Q | | Ctrl+Shift+Option+Q | ctrl+shift+alt+q | Ctrl+Shift+Alt+Q | ctrl+shift+alt+[KeyQ] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyR | r | R | | R | r | R | [KeyR] | | | Ctrl+KeyR | r | Ctrl+R | | Ctrl+R | ctrl+r | Ctrl+R | ctrl+[KeyR] | | | Shift+KeyR | R | Shift+R | | Shift+R | shift+r | Shift+R | shift+[KeyR] | | | Ctrl+Shift+KeyR | R | Ctrl+Shift+R | | Ctrl+Shift+R | ctrl+shift+r | Ctrl+Shift+R | ctrl+shift+[KeyR] | | -| Alt+KeyR | r | Alt+R | | Alt+R | alt+r | Alt+R | alt+[KeyR] | | -| Ctrl+Alt+KeyR | ® | Ctrl+Alt+R | | Ctrl+Alt+R | ctrl+alt+r | Ctrl+Alt+R | ctrl+alt+[KeyR] | | -| Shift+Alt+KeyR | R | Shift+Alt+R | | Shift+Alt+R | shift+alt+r | Shift+Alt+R | shift+alt+[KeyR] | | -| Ctrl+Shift+Alt+KeyR | ‰ | Ctrl+Shift+Alt+R | | Ctrl+Shift+Alt+R | ctrl+shift+alt+r | Ctrl+Shift+Alt+R | ctrl+shift+alt+[KeyR] | | +| Alt+KeyR | r | Alt+R | | Option+R | alt+r | Alt+R | alt+[KeyR] | | +| Ctrl+Alt+KeyR | ® | Ctrl+Alt+R | | Ctrl+Option+R | ctrl+alt+r | Ctrl+Alt+R | ctrl+alt+[KeyR] | | +| Shift+Alt+KeyR | R | Shift+Alt+R | | Shift+Option+R | shift+alt+r | Shift+Alt+R | shift+alt+[KeyR] | | +| Ctrl+Shift+Alt+KeyR | ‰ | Ctrl+Shift+Alt+R | | Ctrl+Shift+Option+R | ctrl+shift+alt+r | Ctrl+Shift+Alt+R | ctrl+shift+alt+[KeyR] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyS | s | S | | S | s | S | [KeyS] | | | Ctrl+KeyS | s | Ctrl+S | | Ctrl+S | ctrl+s | Ctrl+S | ctrl+[KeyS] | | | Shift+KeyS | S | Shift+S | | Shift+S | shift+s | Shift+S | shift+[KeyS] | | | Ctrl+Shift+KeyS | S | Ctrl+Shift+S | | Ctrl+Shift+S | ctrl+shift+s | Ctrl+Shift+S | ctrl+shift+[KeyS] | | -| Alt+KeyS | s | Alt+S | | Alt+S | alt+s | Alt+S | alt+[KeyS] | | -| Ctrl+Alt+KeyS | ß | Ctrl+Alt+S | | Ctrl+Alt+S | ctrl+alt+s | Ctrl+Alt+S | ctrl+alt+[KeyS] | | -| Shift+Alt+KeyS | S | Shift+Alt+S | | Shift+Alt+S | shift+alt+s | Shift+Alt+S | shift+alt+[KeyS] | | -| Ctrl+Shift+Alt+KeyS | Í | Ctrl+Shift+Alt+S | | Ctrl+Shift+Alt+S | ctrl+shift+alt+s | Ctrl+Shift+Alt+S | ctrl+shift+alt+[KeyS] | | +| Alt+KeyS | s | Alt+S | | Option+S | alt+s | Alt+S | alt+[KeyS] | | +| Ctrl+Alt+KeyS | ß | Ctrl+Alt+S | | Ctrl+Option+S | ctrl+alt+s | Ctrl+Alt+S | ctrl+alt+[KeyS] | | +| Shift+Alt+KeyS | S | Shift+Alt+S | | Shift+Option+S | shift+alt+s | Shift+Alt+S | shift+alt+[KeyS] | | +| Ctrl+Shift+Alt+KeyS | Í | Ctrl+Shift+Alt+S | | Ctrl+Shift+Option+S | ctrl+shift+alt+s | Ctrl+Shift+Alt+S | ctrl+shift+alt+[KeyS] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyT | t | T | | T | t | T | [KeyT] | | | Ctrl+KeyT | t | Ctrl+T | | Ctrl+T | ctrl+t | Ctrl+T | ctrl+[KeyT] | | | Shift+KeyT | T | Shift+T | | Shift+T | shift+t | Shift+T | shift+[KeyT] | | | Ctrl+Shift+KeyT | T | Ctrl+Shift+T | | Ctrl+Shift+T | ctrl+shift+t | Ctrl+Shift+T | ctrl+shift+[KeyT] | | -| Alt+KeyT | t | Alt+T | | Alt+T | alt+t | Alt+T | alt+[KeyT] | | -| Ctrl+Alt+KeyT | † | Ctrl+Alt+T | | Ctrl+Alt+T | ctrl+alt+t | Ctrl+Alt+T | ctrl+alt+[KeyT] | | -| Shift+Alt+KeyT | T | Shift+Alt+T | | Shift+Alt+T | shift+alt+t | Shift+Alt+T | shift+alt+[KeyT] | | -| Ctrl+Shift+Alt+KeyT | ˇ | Ctrl+Shift+Alt+T | | Ctrl+Shift+Alt+T | ctrl+shift+alt+t | Ctrl+Shift+Alt+T | ctrl+shift+alt+[KeyT] | | +| Alt+KeyT | t | Alt+T | | Option+T | alt+t | Alt+T | alt+[KeyT] | | +| Ctrl+Alt+KeyT | † | Ctrl+Alt+T | | Ctrl+Option+T | ctrl+alt+t | Ctrl+Alt+T | ctrl+alt+[KeyT] | | +| Shift+Alt+KeyT | T | Shift+Alt+T | | Shift+Option+T | shift+alt+t | Shift+Alt+T | shift+alt+[KeyT] | | +| Ctrl+Shift+Alt+KeyT | ˇ | Ctrl+Shift+Alt+T | | Ctrl+Shift+Option+T | ctrl+shift+alt+t | Ctrl+Shift+Alt+T | ctrl+shift+alt+[KeyT] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -196,37 +196,37 @@ isUSStandard: false | Ctrl+KeyU | u | Ctrl+U | | Ctrl+U | ctrl+u | Ctrl+U | ctrl+[KeyU] | | | Shift+KeyU | U | Shift+U | | Shift+U | shift+u | Shift+U | shift+[KeyU] | | | Ctrl+Shift+KeyU | U | Ctrl+Shift+U | | Ctrl+Shift+U | ctrl+shift+u | Ctrl+Shift+U | ctrl+shift+[KeyU] | | -| Alt+KeyU | u | Alt+U | | Alt+U | alt+u | Alt+U | alt+[KeyU] | | -| Ctrl+Alt+KeyU | ¨ | Ctrl+Alt+U | | Ctrl+Alt+U | ctrl+alt+u | Ctrl+Alt+U | ctrl+alt+[KeyU] | | -| Shift+Alt+KeyU | U | Shift+Alt+U | | Shift+Alt+U | shift+alt+u | Shift+Alt+U | shift+alt+[KeyU] | | -| Ctrl+Shift+Alt+KeyU | ¨ | Ctrl+Shift+Alt+U | | Ctrl+Shift+Alt+U | ctrl+shift+alt+u | Ctrl+Shift+Alt+U | ctrl+shift+alt+[KeyU] | | +| Alt+KeyU | u | Alt+U | | Option+U | alt+u | Alt+U | alt+[KeyU] | | +| Ctrl+Alt+KeyU | ¨ | Ctrl+Alt+U | | Ctrl+Option+U | ctrl+alt+u | Ctrl+Alt+U | ctrl+alt+[KeyU] | | +| Shift+Alt+KeyU | U | Shift+Alt+U | | Shift+Option+U | shift+alt+u | Shift+Alt+U | shift+alt+[KeyU] | | +| Ctrl+Shift+Alt+KeyU | ¨ | Ctrl+Shift+Alt+U | | Ctrl+Shift+Option+U | ctrl+shift+alt+u | Ctrl+Shift+Alt+U | ctrl+shift+alt+[KeyU] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyV | v | V | | V | v | V | [KeyV] | | | Ctrl+KeyV | v | Ctrl+V | | Ctrl+V | ctrl+v | Ctrl+V | ctrl+[KeyV] | | | Shift+KeyV | V | Shift+V | | Shift+V | shift+v | Shift+V | shift+[KeyV] | | | Ctrl+Shift+KeyV | V | Ctrl+Shift+V | | Ctrl+Shift+V | ctrl+shift+v | Ctrl+Shift+V | ctrl+shift+[KeyV] | | -| Alt+KeyV | v | Alt+V | | Alt+V | alt+v | Alt+V | alt+[KeyV] | | -| Ctrl+Alt+KeyV | √ | Ctrl+Alt+V | | Ctrl+Alt+V | ctrl+alt+v | Ctrl+Alt+V | ctrl+alt+[KeyV] | | -| Shift+Alt+KeyV | V | Shift+Alt+V | | Shift+Alt+V | shift+alt+v | Shift+Alt+V | shift+alt+[KeyV] | | -| Ctrl+Shift+Alt+KeyV | ◊ | Ctrl+Shift+Alt+V | | Ctrl+Shift+Alt+V | ctrl+shift+alt+v | Ctrl+Shift+Alt+V | ctrl+shift+alt+[KeyV] | | +| Alt+KeyV | v | Alt+V | | Option+V | alt+v | Alt+V | alt+[KeyV] | | +| Ctrl+Alt+KeyV | √ | Ctrl+Alt+V | | Ctrl+Option+V | ctrl+alt+v | Ctrl+Alt+V | ctrl+alt+[KeyV] | | +| Shift+Alt+KeyV | V | Shift+Alt+V | | Shift+Option+V | shift+alt+v | Shift+Alt+V | shift+alt+[KeyV] | | +| Ctrl+Shift+Alt+KeyV | ◊ | Ctrl+Shift+Alt+V | | Ctrl+Shift+Option+V | ctrl+shift+alt+v | Ctrl+Shift+Alt+V | ctrl+shift+alt+[KeyV] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyW | w | W | | W | w | W | [KeyW] | | | Ctrl+KeyW | w | Ctrl+W | | Ctrl+W | ctrl+w | Ctrl+W | ctrl+[KeyW] | | | Shift+KeyW | W | Shift+W | | Shift+W | shift+w | Shift+W | shift+[KeyW] | | | Ctrl+Shift+KeyW | W | Ctrl+Shift+W | | Ctrl+Shift+W | ctrl+shift+w | Ctrl+Shift+W | ctrl+shift+[KeyW] | | -| Alt+KeyW | w | Alt+W | | Alt+W | alt+w | Alt+W | alt+[KeyW] | | -| Ctrl+Alt+KeyW | ∑ | Ctrl+Alt+W | | Ctrl+Alt+W | ctrl+alt+w | Ctrl+Alt+W | ctrl+alt+[KeyW] | | -| Shift+Alt+KeyW | W | Shift+Alt+W | | Shift+Alt+W | shift+alt+w | Shift+Alt+W | shift+alt+[KeyW] | | -| Ctrl+Shift+Alt+KeyW | „ | Ctrl+Shift+Alt+W | | Ctrl+Shift+Alt+W | ctrl+shift+alt+w | Ctrl+Shift+Alt+W | ctrl+shift+alt+[KeyW] | | +| Alt+KeyW | w | Alt+W | | Option+W | alt+w | Alt+W | alt+[KeyW] | | +| Ctrl+Alt+KeyW | ∑ | Ctrl+Alt+W | | Ctrl+Option+W | ctrl+alt+w | Ctrl+Alt+W | ctrl+alt+[KeyW] | | +| Shift+Alt+KeyW | W | Shift+Alt+W | | Shift+Option+W | shift+alt+w | Shift+Alt+W | shift+alt+[KeyW] | | +| Ctrl+Shift+Alt+KeyW | „ | Ctrl+Shift+Alt+W | | Ctrl+Shift+Option+W | ctrl+shift+alt+w | Ctrl+Shift+Alt+W | ctrl+shift+alt+[KeyW] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyX | x | X | | X | x | X | [KeyX] | | | Ctrl+KeyX | x | Ctrl+X | | Ctrl+X | ctrl+x | Ctrl+X | ctrl+[KeyX] | | | Shift+KeyX | X | Shift+X | | Shift+X | shift+x | Shift+X | shift+[KeyX] | | | Ctrl+Shift+KeyX | X | Ctrl+Shift+X | | Ctrl+Shift+X | ctrl+shift+x | Ctrl+Shift+X | ctrl+shift+[KeyX] | | -| Alt+KeyX | x | Alt+X | | Alt+X | alt+x | Alt+X | alt+[KeyX] | | -| Ctrl+Alt+KeyX | ≈ | Ctrl+Alt+X | | Ctrl+Alt+X | ctrl+alt+x | Ctrl+Alt+X | ctrl+alt+[KeyX] | | -| Shift+Alt+KeyX | X | Shift+Alt+X | | Shift+Alt+X | shift+alt+x | Shift+Alt+X | shift+alt+[KeyX] | | -| Ctrl+Shift+Alt+KeyX | ˛ | Ctrl+Shift+Alt+X | | Ctrl+Shift+Alt+X | ctrl+shift+alt+x | Ctrl+Shift+Alt+X | ctrl+shift+alt+[KeyX] | | +| Alt+KeyX | x | Alt+X | | Option+X | alt+x | Alt+X | alt+[KeyX] | | +| Ctrl+Alt+KeyX | ≈ | Ctrl+Alt+X | | Ctrl+Option+X | ctrl+alt+x | Ctrl+Alt+X | ctrl+alt+[KeyX] | | +| Shift+Alt+KeyX | X | Shift+Alt+X | | Shift+Option+X | shift+alt+x | Shift+Alt+X | shift+alt+[KeyX] | | +| Ctrl+Shift+Alt+KeyX | ˛ | Ctrl+Shift+Alt+X | | Ctrl+Shift+Option+X | ctrl+shift+alt+x | Ctrl+Shift+Alt+X | ctrl+shift+alt+[KeyX] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -234,37 +234,37 @@ isUSStandard: false | Ctrl+KeyY | y | Ctrl+Y | | Ctrl+Y | ctrl+y | Ctrl+Y | ctrl+[KeyY] | | | Shift+KeyY | Y | Shift+Y | | Shift+Y | shift+y | Shift+Y | shift+[KeyY] | | | Ctrl+Shift+KeyY | Y | Ctrl+Shift+Y | | Ctrl+Shift+Y | ctrl+shift+y | Ctrl+Shift+Y | ctrl+shift+[KeyY] | | -| Alt+KeyY | y | Alt+Y | | Alt+Y | alt+y | Alt+Y | alt+[KeyY] | | -| Ctrl+Alt+KeyY | ¥ | Ctrl+Alt+Y | | Ctrl+Alt+Y | ctrl+alt+y | Ctrl+Alt+Y | ctrl+alt+[KeyY] | | -| Shift+Alt+KeyY | Y | Shift+Alt+Y | | Shift+Alt+Y | shift+alt+y | Shift+Alt+Y | shift+alt+[KeyY] | | -| Ctrl+Shift+Alt+KeyY | Á | Ctrl+Shift+Alt+Y | | Ctrl+Shift+Alt+Y | ctrl+shift+alt+y | Ctrl+Shift+Alt+Y | ctrl+shift+alt+[KeyY] | | +| Alt+KeyY | y | Alt+Y | | Option+Y | alt+y | Alt+Y | alt+[KeyY] | | +| Ctrl+Alt+KeyY | ¥ | Ctrl+Alt+Y | | Ctrl+Option+Y | ctrl+alt+y | Ctrl+Alt+Y | ctrl+alt+[KeyY] | | +| Shift+Alt+KeyY | Y | Shift+Alt+Y | | Shift+Option+Y | shift+alt+y | Shift+Alt+Y | shift+alt+[KeyY] | | +| Ctrl+Shift+Alt+KeyY | Á | Ctrl+Shift+Alt+Y | | Ctrl+Shift+Option+Y | ctrl+shift+alt+y | Ctrl+Shift+Alt+Y | ctrl+shift+alt+[KeyY] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | KeyZ | z | Z | | Z | z | Z | [KeyZ] | | | Ctrl+KeyZ | z | Ctrl+Z | | Ctrl+Z | ctrl+z | Ctrl+Z | ctrl+[KeyZ] | | | Shift+KeyZ | Z | Shift+Z | | Shift+Z | shift+z | Shift+Z | shift+[KeyZ] | | | Ctrl+Shift+KeyZ | Z | Ctrl+Shift+Z | | Ctrl+Shift+Z | ctrl+shift+z | Ctrl+Shift+Z | ctrl+shift+[KeyZ] | | -| Alt+KeyZ | z | Alt+Z | | Alt+Z | alt+z | Alt+Z | alt+[KeyZ] | | -| Ctrl+Alt+KeyZ | Ω | Ctrl+Alt+Z | | Ctrl+Alt+Z | ctrl+alt+z | Ctrl+Alt+Z | ctrl+alt+[KeyZ] | | -| Shift+Alt+KeyZ | Z | Shift+Alt+Z | | Shift+Alt+Z | shift+alt+z | Shift+Alt+Z | shift+alt+[KeyZ] | | -| Ctrl+Shift+Alt+KeyZ | ¸ | Ctrl+Shift+Alt+Z | | Ctrl+Shift+Alt+Z | ctrl+shift+alt+z | Ctrl+Shift+Alt+Z | ctrl+shift+alt+[KeyZ] | | +| Alt+KeyZ | z | Alt+Z | | Option+Z | alt+z | Alt+Z | alt+[KeyZ] | | +| Ctrl+Alt+KeyZ | Ω | Ctrl+Alt+Z | | Ctrl+Option+Z | ctrl+alt+z | Ctrl+Alt+Z | ctrl+alt+[KeyZ] | | +| Shift+Alt+KeyZ | Z | Shift+Alt+Z | | Shift+Option+Z | shift+alt+z | Shift+Alt+Z | shift+alt+[KeyZ] | | +| Ctrl+Shift+Alt+KeyZ | ¸ | Ctrl+Shift+Alt+Z | | Ctrl+Shift+Option+Z | ctrl+shift+alt+z | Ctrl+Shift+Alt+Z | ctrl+shift+alt+[KeyZ] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit1 | 1 | 1 | | 1 | 1 | 1 | [Digit1] | | | Ctrl+Digit1 | 1 | Ctrl+1 | | Ctrl+1 | ctrl+1 | Ctrl+1 | ctrl+[Digit1] | | | Shift+Digit1 | ! | Shift+1 | | Shift+1 | shift+1 | Shift+1 | shift+[Digit1] | | | Ctrl+Shift+Digit1 | ! | Ctrl+Shift+1 | | Ctrl+Shift+1 | ctrl+shift+1 | Ctrl+Shift+1 | ctrl+shift+[Digit1] | | -| Alt+Digit1 | 1 | Alt+1 | | Alt+1 | alt+1 | Alt+1 | alt+[Digit1] | | -| Ctrl+Alt+Digit1 | 1 | Ctrl+Alt+1 | | Ctrl+Alt+1 | ctrl+alt+1 | Ctrl+Alt+1 | ctrl+alt+[Digit1] | | -| Shift+Alt+Digit1 | ! | Shift+Alt+1 | | Shift+Alt+1 | shift+alt+1 | Shift+Alt+1 | shift+alt+[Digit1] | | -| Ctrl+Shift+Alt+Digit1 | ⁄ | Ctrl+Shift+Alt+1 | | Ctrl+Shift+Alt+1 | ctrl+shift+alt+1 | Ctrl+Shift+Alt+1 | ctrl+shift+alt+[Digit1] | | +| Alt+Digit1 | 1 | Alt+1 | | Option+1 | alt+1 | Alt+1 | alt+[Digit1] | | +| Ctrl+Alt+Digit1 | 1 | Ctrl+Alt+1 | | Ctrl+Option+1 | ctrl+alt+1 | Ctrl+Alt+1 | ctrl+alt+[Digit1] | | +| Shift+Alt+Digit1 | ! | Shift+Alt+1 | | Shift+Option+1 | shift+alt+1 | Shift+Alt+1 | shift+alt+[Digit1] | | +| Ctrl+Shift+Alt+Digit1 | ⁄ | Ctrl+Shift+Alt+1 | | Ctrl+Shift+Option+1 | ctrl+shift+alt+1 | Ctrl+Shift+Alt+1 | ctrl+shift+alt+[Digit1] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit2 | 2 | 2 | | 2 | 2 | 2 | [Digit2] | | | Ctrl+Digit2 | 2 | Ctrl+2 | | Ctrl+2 | ctrl+2 | Ctrl+2 | ctrl+[Digit2] | | | Shift+Digit2 | @ | Shift+2 | | Shift+2 | shift+2 | Shift+2 | shift+[Digit2] | | | Ctrl+Shift+Digit2 | @ | Ctrl+Shift+2 | | Ctrl+Shift+2 | ctrl+shift+2 | Ctrl+Shift+2 | ctrl+shift+[Digit2] | | -| Alt+Digit2 | 2 | Alt+2 | | Alt+2 | alt+2 | Alt+2 | alt+[Digit2] | | -| Ctrl+Alt+Digit2 | 2 | Ctrl+Alt+2 | | Ctrl+Alt+2 | ctrl+alt+2 | Ctrl+Alt+2 | ctrl+alt+[Digit2] | | -| Shift+Alt+Digit2 | @ | Shift+Alt+2 | | Shift+Alt+2 | shift+alt+2 | Shift+Alt+2 | shift+alt+[Digit2] | | -| Ctrl+Shift+Alt+Digit2 | € | Ctrl+Shift+Alt+2 | | Ctrl+Shift+Alt+2 | ctrl+shift+alt+2 | Ctrl+Shift+Alt+2 | ctrl+shift+alt+[Digit2] | | +| Alt+Digit2 | 2 | Alt+2 | | Option+2 | alt+2 | Alt+2 | alt+[Digit2] | | +| Ctrl+Alt+Digit2 | 2 | Ctrl+Alt+2 | | Ctrl+Option+2 | ctrl+alt+2 | Ctrl+Alt+2 | ctrl+alt+[Digit2] | | +| Shift+Alt+Digit2 | @ | Shift+Alt+2 | | Shift+Option+2 | shift+alt+2 | Shift+Alt+2 | shift+alt+[Digit2] | | +| Ctrl+Shift+Alt+Digit2 | € | Ctrl+Shift+Alt+2 | | Ctrl+Shift+Option+2 | ctrl+shift+alt+2 | Ctrl+Shift+Alt+2 | ctrl+shift+alt+[Digit2] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -272,37 +272,37 @@ isUSStandard: false | Ctrl+Digit3 | 3 | Ctrl+3 | | Ctrl+3 | ctrl+3 | Ctrl+3 | ctrl+[Digit3] | | | Shift+Digit3 | # | Shift+3 | | Shift+3 | shift+3 | Shift+3 | shift+[Digit3] | | | Ctrl+Shift+Digit3 | # | Ctrl+Shift+3 | | Ctrl+Shift+3 | ctrl+shift+3 | Ctrl+Shift+3 | ctrl+shift+[Digit3] | | -| Alt+Digit3 | 3 | Alt+3 | | Alt+3 | alt+3 | Alt+3 | alt+[Digit3] | | -| Ctrl+Alt+Digit3 | 3 | Ctrl+Alt+3 | | Ctrl+Alt+3 | ctrl+alt+3 | Ctrl+Alt+3 | ctrl+alt+[Digit3] | | -| Shift+Alt+Digit3 | # | Shift+Alt+3 | | Shift+Alt+3 | shift+alt+3 | Shift+Alt+3 | shift+alt+[Digit3] | | -| Ctrl+Shift+Alt+Digit3 | ‹ | Ctrl+Shift+Alt+3 | | Ctrl+Shift+Alt+3 | ctrl+shift+alt+3 | Ctrl+Shift+Alt+3 | ctrl+shift+alt+[Digit3] | | +| Alt+Digit3 | 3 | Alt+3 | | Option+3 | alt+3 | Alt+3 | alt+[Digit3] | | +| Ctrl+Alt+Digit3 | 3 | Ctrl+Alt+3 | | Ctrl+Option+3 | ctrl+alt+3 | Ctrl+Alt+3 | ctrl+alt+[Digit3] | | +| Shift+Alt+Digit3 | # | Shift+Alt+3 | | Shift+Option+3 | shift+alt+3 | Shift+Alt+3 | shift+alt+[Digit3] | | +| Ctrl+Shift+Alt+Digit3 | ‹ | Ctrl+Shift+Alt+3 | | Ctrl+Shift+Option+3 | ctrl+shift+alt+3 | Ctrl+Shift+Alt+3 | ctrl+shift+alt+[Digit3] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit4 | 4 | 4 | | 4 | 4 | 4 | [Digit4] | | | Ctrl+Digit4 | 4 | Ctrl+4 | | Ctrl+4 | ctrl+4 | Ctrl+4 | ctrl+[Digit4] | | | Shift+Digit4 | $ | Shift+4 | | Shift+4 | shift+4 | Shift+4 | shift+[Digit4] | | | Ctrl+Shift+Digit4 | $ | Ctrl+Shift+4 | | Ctrl+Shift+4 | ctrl+shift+4 | Ctrl+Shift+4 | ctrl+shift+[Digit4] | | -| Alt+Digit4 | 4 | Alt+4 | | Alt+4 | alt+4 | Alt+4 | alt+[Digit4] | | -| Ctrl+Alt+Digit4 | 4 | Ctrl+Alt+4 | | Ctrl+Alt+4 | ctrl+alt+4 | Ctrl+Alt+4 | ctrl+alt+[Digit4] | | -| Shift+Alt+Digit4 | $ | Shift+Alt+4 | | Shift+Alt+4 | shift+alt+4 | Shift+Alt+4 | shift+alt+[Digit4] | | -| Ctrl+Shift+Alt+Digit4 | › | Ctrl+Shift+Alt+4 | | Ctrl+Shift+Alt+4 | ctrl+shift+alt+4 | Ctrl+Shift+Alt+4 | ctrl+shift+alt+[Digit4] | | +| Alt+Digit4 | 4 | Alt+4 | | Option+4 | alt+4 | Alt+4 | alt+[Digit4] | | +| Ctrl+Alt+Digit4 | 4 | Ctrl+Alt+4 | | Ctrl+Option+4 | ctrl+alt+4 | Ctrl+Alt+4 | ctrl+alt+[Digit4] | | +| Shift+Alt+Digit4 | $ | Shift+Alt+4 | | Shift+Option+4 | shift+alt+4 | Shift+Alt+4 | shift+alt+[Digit4] | | +| Ctrl+Shift+Alt+Digit4 | › | Ctrl+Shift+Alt+4 | | Ctrl+Shift+Option+4 | ctrl+shift+alt+4 | Ctrl+Shift+Alt+4 | ctrl+shift+alt+[Digit4] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit5 | 5 | 5 | | 5 | 5 | 5 | [Digit5] | | | Ctrl+Digit5 | 5 | Ctrl+5 | | Ctrl+5 | ctrl+5 | Ctrl+5 | ctrl+[Digit5] | | | Shift+Digit5 | % | Shift+5 | | Shift+5 | shift+5 | Shift+5 | shift+[Digit5] | | | Ctrl+Shift+Digit5 | % | Ctrl+Shift+5 | | Ctrl+Shift+5 | ctrl+shift+5 | Ctrl+Shift+5 | ctrl+shift+[Digit5] | | -| Alt+Digit5 | 5 | Alt+5 | | Alt+5 | alt+5 | Alt+5 | alt+[Digit5] | | -| Ctrl+Alt+Digit5 | 5 | Ctrl+Alt+5 | | Ctrl+Alt+5 | ctrl+alt+5 | Ctrl+Alt+5 | ctrl+alt+[Digit5] | | -| Shift+Alt+Digit5 | % | Shift+Alt+5 | | Shift+Alt+5 | shift+alt+5 | Shift+Alt+5 | shift+alt+[Digit5] | | -| Ctrl+Shift+Alt+Digit5 | fi | Ctrl+Shift+Alt+5 | | Ctrl+Shift+Alt+5 | ctrl+shift+alt+5 | Ctrl+Shift+Alt+5 | ctrl+shift+alt+[Digit5] | | +| Alt+Digit5 | 5 | Alt+5 | | Option+5 | alt+5 | Alt+5 | alt+[Digit5] | | +| Ctrl+Alt+Digit5 | 5 | Ctrl+Alt+5 | | Ctrl+Option+5 | ctrl+alt+5 | Ctrl+Alt+5 | ctrl+alt+[Digit5] | | +| Shift+Alt+Digit5 | % | Shift+Alt+5 | | Shift+Option+5 | shift+alt+5 | Shift+Alt+5 | shift+alt+[Digit5] | | +| Ctrl+Shift+Alt+Digit5 | fi | Ctrl+Shift+Alt+5 | | Ctrl+Shift+Option+5 | ctrl+shift+alt+5 | Ctrl+Shift+Alt+5 | ctrl+shift+alt+[Digit5] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit6 | 6 | 6 | | 6 | 6 | 6 | [Digit6] | | | Ctrl+Digit6 | 6 | Ctrl+6 | | Ctrl+6 | ctrl+6 | Ctrl+6 | ctrl+[Digit6] | | | Shift+Digit6 | --- | Shift+6 | | Shift+6 | shift+6 | Shift+6 | shift+[Digit6] | | | Ctrl+Shift+Digit6 | --- | Ctrl+Shift+6 | | Ctrl+Shift+6 | ctrl+shift+6 | Ctrl+Shift+6 | ctrl+shift+[Digit6] | | -| Alt+Digit6 | 6 | Alt+6 | | Alt+6 | alt+6 | Alt+6 | alt+[Digit6] | | -| Ctrl+Alt+Digit6 | 6 | Ctrl+Alt+6 | | Ctrl+Alt+6 | ctrl+alt+6 | Ctrl+Alt+6 | ctrl+alt+[Digit6] | | -| Shift+Alt+Digit6 | --- | Shift+Alt+6 | | Shift+Alt+6 | shift+alt+6 | Shift+Alt+6 | shift+alt+[Digit6] | | -| Ctrl+Shift+Alt+Digit6 | fl | Ctrl+Shift+Alt+6 | | Ctrl+Shift+Alt+6 | ctrl+shift+alt+6 | Ctrl+Shift+Alt+6 | ctrl+shift+alt+[Digit6] | | +| Alt+Digit6 | 6 | Alt+6 | | Option+6 | alt+6 | Alt+6 | alt+[Digit6] | | +| Ctrl+Alt+Digit6 | 6 | Ctrl+Alt+6 | | Ctrl+Option+6 | ctrl+alt+6 | Ctrl+Alt+6 | ctrl+alt+[Digit6] | | +| Shift+Alt+Digit6 | --- | Shift+Alt+6 | | Shift+Option+6 | shift+alt+6 | Shift+Alt+6 | shift+alt+[Digit6] | | +| Ctrl+Shift+Alt+Digit6 | fl | Ctrl+Shift+Alt+6 | | Ctrl+Shift+Option+6 | ctrl+shift+alt+6 | Ctrl+Shift+Alt+6 | ctrl+shift+alt+[Digit6] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -310,37 +310,37 @@ isUSStandard: false | Ctrl+Digit7 | 7 | Ctrl+7 | | Ctrl+7 | ctrl+7 | Ctrl+7 | ctrl+[Digit7] | | | Shift+Digit7 | & | Shift+7 | | Shift+7 | shift+7 | Shift+7 | shift+[Digit7] | | | Ctrl+Shift+Digit7 | & | Ctrl+Shift+7 | | Ctrl+Shift+7 | ctrl+shift+7 | Ctrl+Shift+7 | ctrl+shift+[Digit7] | | -| Alt+Digit7 | 7 | Alt+7 | | Alt+7 | alt+7 | Alt+7 | alt+[Digit7] | | -| Ctrl+Alt+Digit7 | 7 | Ctrl+Alt+7 | | Ctrl+Alt+7 | ctrl+alt+7 | Ctrl+Alt+7 | ctrl+alt+[Digit7] | | -| Shift+Alt+Digit7 | & | Shift+Alt+7 | | Shift+Alt+7 | shift+alt+7 | Shift+Alt+7 | shift+alt+[Digit7] | | -| Ctrl+Shift+Alt+Digit7 | ‡ | Ctrl+Shift+Alt+7 | | Ctrl+Shift+Alt+7 | ctrl+shift+alt+7 | Ctrl+Shift+Alt+7 | ctrl+shift+alt+[Digit7] | | +| Alt+Digit7 | 7 | Alt+7 | | Option+7 | alt+7 | Alt+7 | alt+[Digit7] | | +| Ctrl+Alt+Digit7 | 7 | Ctrl+Alt+7 | | Ctrl+Option+7 | ctrl+alt+7 | Ctrl+Alt+7 | ctrl+alt+[Digit7] | | +| Shift+Alt+Digit7 | & | Shift+Alt+7 | | Shift+Option+7 | shift+alt+7 | Shift+Alt+7 | shift+alt+[Digit7] | | +| Ctrl+Shift+Alt+Digit7 | ‡ | Ctrl+Shift+Alt+7 | | Ctrl+Shift+Option+7 | ctrl+shift+alt+7 | Ctrl+Shift+Alt+7 | ctrl+shift+alt+[Digit7] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit8 | 8 | 8 | | 8 | 8 | 8 | [Digit8] | | | Ctrl+Digit8 | 8 | Ctrl+8 | | Ctrl+8 | ctrl+8 | Ctrl+8 | ctrl+[Digit8] | | | Shift+Digit8 | * | Shift+8 | | Shift+8 | shift+8 | Shift+8 | shift+[Digit8] | | | Ctrl+Shift+Digit8 | * | Ctrl+Shift+8 | | Ctrl+Shift+8 | ctrl+shift+8 | Ctrl+Shift+8 | ctrl+shift+[Digit8] | | -| Alt+Digit8 | 8 | Alt+8 | | Alt+8 | alt+8 | Alt+8 | alt+[Digit8] | | -| Ctrl+Alt+Digit8 | 8 | Ctrl+Alt+8 | | Ctrl+Alt+8 | ctrl+alt+8 | Ctrl+Alt+8 | ctrl+alt+[Digit8] | | -| Shift+Alt+Digit8 | * | Shift+Alt+8 | | Shift+Alt+8 | shift+alt+8 | Shift+Alt+8 | shift+alt+[Digit8] | | -| Ctrl+Shift+Alt+Digit8 | ° | Ctrl+Shift+Alt+8 | | Ctrl+Shift+Alt+8 | ctrl+shift+alt+8 | Ctrl+Shift+Alt+8 | ctrl+shift+alt+[Digit8] | | +| Alt+Digit8 | 8 | Alt+8 | | Option+8 | alt+8 | Alt+8 | alt+[Digit8] | | +| Ctrl+Alt+Digit8 | 8 | Ctrl+Alt+8 | | Ctrl+Option+8 | ctrl+alt+8 | Ctrl+Alt+8 | ctrl+alt+[Digit8] | | +| Shift+Alt+Digit8 | * | Shift+Alt+8 | | Shift+Option+8 | shift+alt+8 | Shift+Alt+8 | shift+alt+[Digit8] | | +| Ctrl+Shift+Alt+Digit8 | ° | Ctrl+Shift+Alt+8 | | Ctrl+Shift+Option+8 | ctrl+shift+alt+8 | Ctrl+Shift+Alt+8 | ctrl+shift+alt+[Digit8] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit9 | 9 | 9 | | 9 | 9 | 9 | [Digit9] | | | Ctrl+Digit9 | 9 | Ctrl+9 | | Ctrl+9 | ctrl+9 | Ctrl+9 | ctrl+[Digit9] | | | Shift+Digit9 | ( | Shift+9 | | Shift+9 | shift+9 | Shift+9 | shift+[Digit9] | | | Ctrl+Shift+Digit9 | ( | Ctrl+Shift+9 | | Ctrl+Shift+9 | ctrl+shift+9 | Ctrl+Shift+9 | ctrl+shift+[Digit9] | | -| Alt+Digit9 | 9 | Alt+9 | | Alt+9 | alt+9 | Alt+9 | alt+[Digit9] | | -| Ctrl+Alt+Digit9 | 9 | Ctrl+Alt+9 | | Ctrl+Alt+9 | ctrl+alt+9 | Ctrl+Alt+9 | ctrl+alt+[Digit9] | | -| Shift+Alt+Digit9 | ( | Shift+Alt+9 | | Shift+Alt+9 | shift+alt+9 | Shift+Alt+9 | shift+alt+[Digit9] | | -| Ctrl+Shift+Alt+Digit9 | · | Ctrl+Shift+Alt+9 | | Ctrl+Shift+Alt+9 | ctrl+shift+alt+9 | Ctrl+Shift+Alt+9 | ctrl+shift+alt+[Digit9] | | +| Alt+Digit9 | 9 | Alt+9 | | Option+9 | alt+9 | Alt+9 | alt+[Digit9] | | +| Ctrl+Alt+Digit9 | 9 | Ctrl+Alt+9 | | Ctrl+Option+9 | ctrl+alt+9 | Ctrl+Alt+9 | ctrl+alt+[Digit9] | | +| Shift+Alt+Digit9 | ( | Shift+Alt+9 | | Shift+Option+9 | shift+alt+9 | Shift+Alt+9 | shift+alt+[Digit9] | | +| Ctrl+Shift+Alt+Digit9 | · | Ctrl+Shift+Alt+9 | | Ctrl+Shift+Option+9 | ctrl+shift+alt+9 | Ctrl+Shift+Alt+9 | ctrl+shift+alt+[Digit9] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Digit0 | 0 | 0 | | 0 | 0 | 0 | [Digit0] | | | Ctrl+Digit0 | 0 | Ctrl+0 | | Ctrl+0 | ctrl+0 | Ctrl+0 | ctrl+[Digit0] | | | Shift+Digit0 | ) | Shift+0 | | Shift+0 | shift+0 | Shift+0 | shift+[Digit0] | | | Ctrl+Shift+Digit0 | ) | Ctrl+Shift+0 | | Ctrl+Shift+0 | ctrl+shift+0 | Ctrl+Shift+0 | ctrl+shift+[Digit0] | | -| Alt+Digit0 | 0 | Alt+0 | | Alt+0 | alt+0 | Alt+0 | alt+[Digit0] | | -| Ctrl+Alt+Digit0 | 0 | Ctrl+Alt+0 | | Ctrl+Alt+0 | ctrl+alt+0 | Ctrl+Alt+0 | ctrl+alt+[Digit0] | | -| Shift+Alt+Digit0 | ) | Shift+Alt+0 | | Shift+Alt+0 | shift+alt+0 | Shift+Alt+0 | shift+alt+[Digit0] | | -| Ctrl+Shift+Alt+Digit0 | ‚ | Ctrl+Shift+Alt+0 | | Ctrl+Shift+Alt+0 | ctrl+shift+alt+0 | Ctrl+Shift+Alt+0 | ctrl+shift+alt+[Digit0] | | +| Alt+Digit0 | 0 | Alt+0 | | Option+0 | alt+0 | Alt+0 | alt+[Digit0] | | +| Ctrl+Alt+Digit0 | 0 | Ctrl+Alt+0 | | Ctrl+Option+0 | ctrl+alt+0 | Ctrl+Alt+0 | ctrl+alt+[Digit0] | | +| Shift+Alt+Digit0 | ) | Shift+Alt+0 | | Shift+Option+0 | shift+alt+0 | Shift+Alt+0 | shift+alt+[Digit0] | | +| Ctrl+Shift+Alt+Digit0 | ‚ | Ctrl+Shift+Alt+0 | | Ctrl+Shift+Option+0 | ctrl+shift+alt+0 | Ctrl+Shift+Alt+0 | ctrl+shift+alt+[Digit0] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -348,37 +348,37 @@ isUSStandard: false | Ctrl+Minus | - | Ctrl+- | | Ctrl+- | ctrl+- | Ctrl+- | ctrl+[Minus] | | | Shift+Minus | --- | Shift+- | | Shift+- | shift+- | Shift+- | shift+[Minus] | | | Ctrl+Shift+Minus | --- | Ctrl+Shift+- | | Ctrl+Shift+- | ctrl+shift+- | Ctrl+Shift+- | ctrl+shift+[Minus] | | -| Alt+Minus | - | Alt+- | | Alt+- | alt+- | Alt+- | alt+[Minus] | | -| Ctrl+Alt+Minus | – | Ctrl+Alt+- | | Ctrl+Alt+- | ctrl+alt+- | Ctrl+Alt+- | ctrl+alt+[Minus] | | -| Shift+Alt+Minus | --- | Shift+Alt+- | | Shift+Alt+- | shift+alt+- | Shift+Alt+- | shift+alt+[Minus] | | -| Ctrl+Shift+Alt+Minus | — | Ctrl+Shift+Alt+- | | Ctrl+Shift+Alt+- | ctrl+shift+alt+- | Ctrl+Shift+Alt+- | ctrl+shift+alt+[Minus] | | +| Alt+Minus | - | Alt+- | | Option+- | alt+- | Alt+- | alt+[Minus] | | +| Ctrl+Alt+Minus | – | Ctrl+Alt+- | | Ctrl+Option+- | ctrl+alt+- | Ctrl+Alt+- | ctrl+alt+[Minus] | | +| Shift+Alt+Minus | --- | Shift+Alt+- | | Shift+Option+- | shift+alt+- | Shift+Alt+- | shift+alt+[Minus] | | +| Ctrl+Shift+Alt+Minus | — | Ctrl+Shift+Alt+- | | Ctrl+Shift+Option+- | ctrl+shift+alt+- | Ctrl+Shift+Alt+- | ctrl+shift+alt+[Minus] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Equal | = | = | | = | = | = | [Equal] | | | Ctrl+Equal | = | Ctrl+= | | Ctrl+= | ctrl+= | Ctrl+= | ctrl+[Equal] | | | Shift+Equal | + | Shift+= | | Shift+= | shift+= | Shift+= | shift+[Equal] | | | Ctrl+Shift+Equal | + | Ctrl+Shift+= | | Ctrl+Shift+= | ctrl+shift+= | Ctrl+Shift+= | ctrl+shift+[Equal] | | -| Alt+Equal | = | Alt+= | | Alt+= | alt+= | Alt+= | alt+[Equal] | | -| Ctrl+Alt+Equal | ≠ | Ctrl+Alt+= | | Ctrl+Alt+= | ctrl+alt+= | Ctrl+Alt+= | ctrl+alt+[Equal] | | -| Shift+Alt+Equal | + | Shift+Alt+= | | Shift+Alt+= | shift+alt+= | Shift+Alt+= | shift+alt+[Equal] | | -| Ctrl+Shift+Alt+Equal | ± | Ctrl+Shift+Alt+= | | Ctrl+Shift+Alt+= | ctrl+shift+alt+= | Ctrl+Shift+Alt+= | ctrl+shift+alt+[Equal] | | +| Alt+Equal | = | Alt+= | | Option+= | alt+= | Alt+= | alt+[Equal] | | +| Ctrl+Alt+Equal | ≠ | Ctrl+Alt+= | | Ctrl+Option+= | ctrl+alt+= | Ctrl+Alt+= | ctrl+alt+[Equal] | | +| Shift+Alt+Equal | + | Shift+Alt+= | | Shift+Option+= | shift+alt+= | Shift+Alt+= | shift+alt+[Equal] | | +| Ctrl+Shift+Alt+Equal | ± | Ctrl+Shift+Alt+= | | Ctrl+Shift+Option+= | ctrl+shift+alt+= | Ctrl+Shift+Alt+= | ctrl+shift+alt+[Equal] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | BracketLeft | 【 | [ | 1 | 【 | [BracketLeft] | null | [BracketLeft] | NO | | Ctrl+BracketLeft | 【 | Ctrl+[ | 1 | Ctrl+【 | ctrl+[BracketLeft] | null | ctrl+[BracketLeft] | NO | | Shift+BracketLeft | 「 | [ | 2 | Shift+【 | shift+[BracketLeft] | null | shift+[BracketLeft] | NO | | Ctrl+Shift+BracketLeft | 「 | Ctrl+[ | 2 | Ctrl+Shift+【 | ctrl+shift+[BracketLeft] | null | ctrl+shift+[BracketLeft] | NO | -| Alt+BracketLeft | 【 | Alt+[ | 1 | Alt+【 | alt+[BracketLeft] | null | alt+[BracketLeft] | NO | -| Ctrl+Alt+BracketLeft | “ | Ctrl+Alt+[ | 1 | Ctrl+Alt+【 | ctrl+alt+[BracketLeft] | null | ctrl+alt+[BracketLeft] | NO | -| Shift+Alt+BracketLeft | 「 | Alt+[ | 2 | Shift+Alt+【 | shift+alt+[BracketLeft] | null | shift+alt+[BracketLeft] | NO | -| Ctrl+Shift+Alt+BracketLeft | ” | Ctrl+Alt+[ | 2 | Ctrl+Shift+Alt+【 | ctrl+shift+alt+[BracketLeft] | null | ctrl+shift+alt+[BracketLeft] | NO | +| Alt+BracketLeft | 【 | Alt+[ | 1 | Option+【 | alt+[BracketLeft] | null | alt+[BracketLeft] | NO | +| Ctrl+Alt+BracketLeft | “ | Ctrl+Alt+[ | 1 | Ctrl+Option+【 | ctrl+alt+[BracketLeft] | null | ctrl+alt+[BracketLeft] | NO | +| Shift+Alt+BracketLeft | 「 | Alt+[ | 2 | Shift+Option+【 | shift+alt+[BracketLeft] | null | shift+alt+[BracketLeft] | NO | +| Ctrl+Shift+Alt+BracketLeft | ” | Ctrl+Alt+[ | 2 | Ctrl+Shift+Option+【 | ctrl+shift+alt+[BracketLeft] | null | ctrl+shift+alt+[BracketLeft] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | BracketRight | 】 | ] | 1 | 】 | [BracketRight] | null | [BracketRight] | NO | | Ctrl+BracketRight | 】 | Ctrl+] | 1 | Ctrl+】 | ctrl+[BracketRight] | null | ctrl+[BracketRight] | NO | | Shift+BracketRight | 」 | ] | 2 | Shift+】 | shift+[BracketRight] | null | shift+[BracketRight] | NO | | Ctrl+Shift+BracketRight | 」 | Ctrl+] | 2 | Ctrl+Shift+】 | ctrl+shift+[BracketRight] | null | ctrl+shift+[BracketRight] | NO | -| Alt+BracketRight | 】 | Alt+] | 1 | Alt+】 | alt+[BracketRight] | null | alt+[BracketRight] | NO | -| Ctrl+Alt+BracketRight | ‘ | Ctrl+Alt+] | 1 | Ctrl+Alt+】 | ctrl+alt+[BracketRight] | null | ctrl+alt+[BracketRight] | NO | -| Shift+Alt+BracketRight | 」 | Alt+] | 2 | Shift+Alt+】 | shift+alt+[BracketRight] | null | shift+alt+[BracketRight] | NO | -| Ctrl+Shift+Alt+BracketRight | ’ | Ctrl+Alt+] | 2 | Ctrl+Shift+Alt+】 | ctrl+shift+alt+[BracketRight] | null | ctrl+shift+alt+[BracketRight] | NO | +| Alt+BracketRight | 】 | Alt+] | 1 | Option+】 | alt+[BracketRight] | null | alt+[BracketRight] | NO | +| Ctrl+Alt+BracketRight | ‘ | Ctrl+Alt+] | 1 | Ctrl+Option+】 | ctrl+alt+[BracketRight] | null | ctrl+alt+[BracketRight] | NO | +| Shift+Alt+BracketRight | 」 | Alt+] | 2 | Shift+Option+】 | shift+alt+[BracketRight] | null | shift+alt+[BracketRight] | NO | +| Ctrl+Shift+Alt+BracketRight | ’ | Ctrl+Alt+] | 2 | Ctrl+Shift+Option+】 | ctrl+shift+alt+[BracketRight] | null | ctrl+shift+alt+[BracketRight] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -386,10 +386,10 @@ isUSStandard: false | Ctrl+Backslash | 、 | | | Ctrl+、 | ctrl+[Backslash] | null | ctrl+[Backslash] | NO | | Shift+Backslash | | | | | Shift+、 | shift+[Backslash] | null | shift+[Backslash] | NO | | Ctrl+Shift+Backslash | | | | | Ctrl+Shift+、 | ctrl+shift+[Backslash] | null | ctrl+shift+[Backslash] | NO | -| Alt+Backslash | 、 | | | Alt+、 | alt+[Backslash] | null | alt+[Backslash] | NO | -| Ctrl+Alt+Backslash | « | | | Ctrl+Alt+、 | ctrl+alt+[Backslash] | null | ctrl+alt+[Backslash] | NO | -| Shift+Alt+Backslash | | | | | Shift+Alt+、 | shift+alt+[Backslash] | null | shift+alt+[Backslash] | NO | -| Ctrl+Shift+Alt+Backslash | » | | | Ctrl+Shift+Alt+、 | ctrl+shift+alt+[Backslash] | null | ctrl+shift+alt+[Backslash] | NO | +| Alt+Backslash | 、 | | | Option+、 | alt+[Backslash] | null | alt+[Backslash] | NO | +| Ctrl+Alt+Backslash | « | | | Ctrl+Option+、 | ctrl+alt+[Backslash] | null | ctrl+alt+[Backslash] | NO | +| Shift+Alt+Backslash | | | | | Shift+Option+、 | shift+alt+[Backslash] | null | shift+alt+[Backslash] | NO | +| Ctrl+Shift+Alt+Backslash | » | | | Ctrl+Shift+Option+、 | ctrl+shift+alt+[Backslash] | null | ctrl+shift+alt+[Backslash] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | IntlHash | --- | | | null | null | null | null | | | Ctrl+IntlHash | --- | | | null | null | null | null | | @@ -404,19 +404,19 @@ isUSStandard: false | Ctrl+Semicolon | ; | Ctrl+; | | Ctrl+; | ctrl+; | Ctrl+; | ctrl+[Semicolon] | NO | | Shift+Semicolon | : | Shift+; | | Shift+; | shift+; | Shift+; | shift+[Semicolon] | NO | | Ctrl+Shift+Semicolon | : | Ctrl+Shift+; | | Ctrl+Shift+; | ctrl+shift+; | Ctrl+Shift+; | ctrl+shift+[Semicolon] | NO | -| Alt+Semicolon | ; | Alt+; | | Alt+; | alt+; | Alt+; | alt+[Semicolon] | NO | -| Ctrl+Alt+Semicolon | … | Ctrl+Alt+; | | Ctrl+Alt+; | ctrl+alt+; | Ctrl+Alt+; | ctrl+alt+[Semicolon] | NO | -| Shift+Alt+Semicolon | : | Shift+Alt+; | | Shift+Alt+; | shift+alt+; | Shift+Alt+; | shift+alt+[Semicolon] | NO | -| Ctrl+Shift+Alt+Semicolon | Ú | Ctrl+Shift+Alt+; | | Ctrl+Shift+Alt+; | ctrl+shift+alt+; | Ctrl+Shift+Alt+; | ctrl+shift+alt+[Semicolon] | NO | +| Alt+Semicolon | ; | Alt+; | | Option+; | alt+; | Alt+; | alt+[Semicolon] | NO | +| Ctrl+Alt+Semicolon | … | Ctrl+Alt+; | | Ctrl+Option+; | ctrl+alt+; | Ctrl+Alt+; | ctrl+alt+[Semicolon] | NO | +| Shift+Alt+Semicolon | : | Shift+Alt+; | | Shift+Option+; | shift+alt+; | Shift+Alt+; | shift+alt+[Semicolon] | NO | +| Ctrl+Shift+Alt+Semicolon | Ú | Ctrl+Shift+Alt+; | | Ctrl+Shift+Option+; | ctrl+shift+alt+; | Ctrl+Shift+Alt+; | ctrl+shift+alt+[Semicolon] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Quote | ' | ' | | ' | ' | ' | [Quote] | | | Ctrl+Quote | ' | Ctrl+' | | Ctrl+' | ctrl+' | Ctrl+' | ctrl+[Quote] | | | Shift+Quote | " | Shift+' | | Shift+' | shift+' | Shift+' | shift+[Quote] | | | Ctrl+Shift+Quote | " | Ctrl+Shift+' | | Ctrl+Shift+' | ctrl+shift+' | Ctrl+Shift+' | ctrl+shift+[Quote] | | -| Alt+Quote | ' | Alt+' | | Alt+' | alt+' | Alt+' | alt+[Quote] | | -| Ctrl+Alt+Quote | æ | Ctrl+Alt+' | | Ctrl+Alt+' | ctrl+alt+' | Ctrl+Alt+' | ctrl+alt+[Quote] | | -| Shift+Alt+Quote | " | Shift+Alt+' | | Shift+Alt+' | shift+alt+' | Shift+Alt+' | shift+alt+[Quote] | | -| Ctrl+Shift+Alt+Quote | Æ | Ctrl+Shift+Alt+' | | Ctrl+Shift+Alt+' | ctrl+shift+alt+' | Ctrl+Shift+Alt+' | ctrl+shift+alt+[Quote] | | +| Alt+Quote | ' | Alt+' | | Option+' | alt+' | Alt+' | alt+[Quote] | | +| Ctrl+Alt+Quote | æ | Ctrl+Alt+' | | Ctrl+Option+' | ctrl+alt+' | Ctrl+Alt+' | ctrl+alt+[Quote] | | +| Shift+Alt+Quote | " | Shift+Alt+' | | Shift+Option+' | shift+alt+' | Shift+Alt+' | shift+alt+[Quote] | | +| Ctrl+Shift+Alt+Quote | Æ | Ctrl+Shift+Alt+' | | Ctrl+Shift+Option+' | ctrl+shift+alt+' | Ctrl+Shift+Alt+' | ctrl+shift+alt+[Quote] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -424,37 +424,37 @@ isUSStandard: false | Ctrl+Backquote | · | | | Ctrl+· | ctrl+[Backquote] | null | ctrl+[Backquote] | NO | | Shift+Backquote | ~ | | | Shift+· | shift+[Backquote] | null | shift+[Backquote] | NO | | Ctrl+Shift+Backquote | ~ | | | Ctrl+Shift+· | ctrl+shift+[Backquote] | null | ctrl+shift+[Backquote] | NO | -| Alt+Backquote | · | | | Alt+· | alt+[Backquote] | null | alt+[Backquote] | NO | -| Ctrl+Alt+Backquote | · | | | Ctrl+Alt+· | ctrl+alt+[Backquote] | null | ctrl+alt+[Backquote] | NO | -| Shift+Alt+Backquote | ~ | | | Shift+Alt+· | shift+alt+[Backquote] | null | shift+alt+[Backquote] | NO | -| Ctrl+Shift+Alt+Backquote | · | | | Ctrl+Shift+Alt+· | ctrl+shift+alt+[Backquote] | null | ctrl+shift+alt+[Backquote] | NO | +| Alt+Backquote | · | | | Option+· | alt+[Backquote] | null | alt+[Backquote] | NO | +| Ctrl+Alt+Backquote | · | | | Ctrl+Option+· | ctrl+alt+[Backquote] | null | ctrl+alt+[Backquote] | NO | +| Shift+Alt+Backquote | ~ | | | Shift+Option+· | shift+alt+[Backquote] | null | shift+alt+[Backquote] | NO | +| Ctrl+Shift+Alt+Backquote | · | | | Ctrl+Shift+Option+· | ctrl+shift+alt+[Backquote] | null | ctrl+shift+alt+[Backquote] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Comma | , | , | | , | , | , | [Comma] | NO | | Ctrl+Comma | , | Ctrl+, | | Ctrl+, | ctrl+, | Ctrl+, | ctrl+[Comma] | NO | | Shift+Comma | 《 | Shift+, | | Shift+, | shift+, | Shift+, | shift+[Comma] | NO | | Ctrl+Shift+Comma | 《 | Ctrl+Shift+, | | Ctrl+Shift+, | ctrl+shift+, | Ctrl+Shift+, | ctrl+shift+[Comma] | NO | -| Alt+Comma | , | Alt+, | | Alt+, | alt+, | Alt+, | alt+[Comma] | NO | -| Ctrl+Alt+Comma | ≤ | Ctrl+Alt+, | | Ctrl+Alt+, | ctrl+alt+, | Ctrl+Alt+, | ctrl+alt+[Comma] | NO | -| Shift+Alt+Comma | 《 | Shift+Alt+, | | Shift+Alt+, | shift+alt+, | Shift+Alt+, | shift+alt+[Comma] | NO | -| Ctrl+Shift+Alt+Comma | ¯ | Ctrl+Shift+Alt+, | | Ctrl+Shift+Alt+, | ctrl+shift+alt+, | Ctrl+Shift+Alt+, | ctrl+shift+alt+[Comma] | NO | +| Alt+Comma | , | Alt+, | | Option+, | alt+, | Alt+, | alt+[Comma] | NO | +| Ctrl+Alt+Comma | ≤ | Ctrl+Alt+, | | Ctrl+Option+, | ctrl+alt+, | Ctrl+Alt+, | ctrl+alt+[Comma] | NO | +| Shift+Alt+Comma | 《 | Shift+Alt+, | | Shift+Option+, | shift+alt+, | Shift+Alt+, | shift+alt+[Comma] | NO | +| Ctrl+Shift+Alt+Comma | ¯ | Ctrl+Shift+Alt+, | | Ctrl+Shift+Option+, | ctrl+shift+alt+, | Ctrl+Shift+Alt+, | ctrl+shift+alt+[Comma] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Period | 。 | . | | 。 | . | . | [Period] | NO | | Ctrl+Period | 。 | Ctrl+. | | Ctrl+。 | ctrl+. | Ctrl+. | ctrl+[Period] | NO | | Shift+Period | 》 | Shift+. | | Shift+。 | shift+. | Shift+. | shift+[Period] | NO | | Ctrl+Shift+Period | 》 | Ctrl+Shift+. | | Ctrl+Shift+。 | ctrl+shift+. | Ctrl+Shift+. | ctrl+shift+[Period] | NO | -| Alt+Period | 。 | Alt+. | | Alt+。 | alt+. | Alt+. | alt+[Period] | NO | -| Ctrl+Alt+Period | ≥ | Ctrl+Alt+. | | Ctrl+Alt+。 | ctrl+alt+. | Ctrl+Alt+. | ctrl+alt+[Period] | NO | -| Shift+Alt+Period | 》 | Shift+Alt+. | | Shift+Alt+。 | shift+alt+. | Shift+Alt+. | shift+alt+[Period] | NO | -| Ctrl+Shift+Alt+Period | ˘ | Ctrl+Shift+Alt+. | | Ctrl+Shift+Alt+。 | ctrl+shift+alt+. | Ctrl+Shift+Alt+. | ctrl+shift+alt+[Period] | NO | +| Alt+Period | 。 | Alt+. | | Option+。 | alt+. | Alt+. | alt+[Period] | NO | +| Ctrl+Alt+Period | ≥ | Ctrl+Alt+. | | Ctrl+Option+。 | ctrl+alt+. | Ctrl+Alt+. | ctrl+alt+[Period] | NO | +| Shift+Alt+Period | 》 | Shift+Alt+. | | Shift+Option+。 | shift+alt+. | Shift+Alt+. | shift+alt+[Period] | NO | +| Ctrl+Shift+Alt+Period | ˘ | Ctrl+Shift+Alt+. | | Ctrl+Shift+Option+。 | ctrl+shift+alt+. | Ctrl+Shift+Alt+. | ctrl+shift+alt+[Period] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Slash | / | / | | / | / | / | [Slash] | | | Ctrl+Slash | / | Ctrl+/ | | Ctrl+/ | ctrl+/ | Ctrl+/ | ctrl+[Slash] | | | Shift+Slash | ? | Shift+/ | | Shift+/ | shift+/ | Shift+/ | shift+[Slash] | | | Ctrl+Shift+Slash | ? | Ctrl+Shift+/ | | Ctrl+Shift+/ | ctrl+shift+/ | Ctrl+Shift+/ | ctrl+shift+[Slash] | | -| Alt+Slash | / | Alt+/ | | Alt+/ | alt+/ | Alt+/ | alt+[Slash] | | -| Ctrl+Alt+Slash | ÷ | Ctrl+Alt+/ | | Ctrl+Alt+/ | ctrl+alt+/ | Ctrl+Alt+/ | ctrl+alt+[Slash] | | -| Shift+Alt+Slash | ? | Shift+Alt+/ | | Shift+Alt+/ | shift+alt+/ | Shift+Alt+/ | shift+alt+[Slash] | | -| Ctrl+Shift+Alt+Slash | ¿ | Ctrl+Shift+Alt+/ | | Ctrl+Shift+Alt+/ | ctrl+shift+alt+/ | Ctrl+Shift+Alt+/ | ctrl+shift+alt+[Slash] | | +| Alt+Slash | / | Alt+/ | | Option+/ | alt+/ | Alt+/ | alt+[Slash] | | +| Ctrl+Alt+Slash | ÷ | Ctrl+Alt+/ | | Ctrl+Option+/ | ctrl+alt+/ | Ctrl+Alt+/ | ctrl+alt+[Slash] | | +| Shift+Alt+Slash | ? | Shift+Alt+/ | | Shift+Option+/ | shift+alt+/ | Shift+Alt+/ | shift+alt+[Slash] | | +| Ctrl+Shift+Alt+Slash | ¿ | Ctrl+Shift+Alt+/ | | Ctrl+Shift+Option+/ | ctrl+shift+alt+/ | Ctrl+Shift+Alt+/ | ctrl+shift+alt+[Slash] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | HW Code combination | Key | KeyCode combination | Pri | UI label | User settings | Electron accelerator | Dispatching string | WYSIWYG | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -462,28 +462,28 @@ isUSStandard: false | Ctrl+ArrowUp | --- | Ctrl+UpArrow | | Ctrl+UpArrow | ctrl+up | Ctrl+Up | ctrl+[ArrowUp] | | | Shift+ArrowUp | --- | Shift+UpArrow | | Shift+UpArrow | shift+up | Shift+Up | shift+[ArrowUp] | | | Ctrl+Shift+ArrowUp | --- | Ctrl+Shift+UpArrow | | Ctrl+Shift+UpArrow | ctrl+shift+up | Ctrl+Shift+Up | ctrl+shift+[ArrowUp] | | -| Alt+ArrowUp | --- | Alt+UpArrow | | Alt+UpArrow | alt+up | Alt+Up | alt+[ArrowUp] | | -| Ctrl+Alt+ArrowUp | --- | Ctrl+Alt+UpArrow | | Ctrl+Alt+UpArrow | ctrl+alt+up | Ctrl+Alt+Up | ctrl+alt+[ArrowUp] | | -| Shift+Alt+ArrowUp | --- | Shift+Alt+UpArrow | | Shift+Alt+UpArrow | shift+alt+up | Shift+Alt+Up | shift+alt+[ArrowUp] | | -| Ctrl+Shift+Alt+ArrowUp | --- | Ctrl+Shift+Alt+UpArrow | | Ctrl+Shift+Alt+UpArrow | ctrl+shift+alt+up | Ctrl+Shift+Alt+Up | ctrl+shift+alt+[ArrowUp] | | +| Alt+ArrowUp | --- | Alt+UpArrow | | Option+UpArrow | alt+up | Alt+Up | alt+[ArrowUp] | | +| Ctrl+Alt+ArrowUp | --- | Ctrl+Alt+UpArrow | | Ctrl+Option+UpArrow | ctrl+alt+up | Ctrl+Alt+Up | ctrl+alt+[ArrowUp] | | +| Shift+Alt+ArrowUp | --- | Shift+Alt+UpArrow | | Shift+Option+UpArrow | shift+alt+up | Shift+Alt+Up | shift+alt+[ArrowUp] | | +| Ctrl+Shift+Alt+ArrowUp | --- | Ctrl+Shift+Alt+UpArrow | | Ctrl+Shift+Option+UpArrow | ctrl+shift+alt+up | Ctrl+Shift+Alt+Up | ctrl+shift+alt+[ArrowUp] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Numpad0 | --- | NumPad0 | | NumPad0 | numpad0 | null | [Numpad0] | | | Ctrl+Numpad0 | --- | Ctrl+NumPad0 | | Ctrl+NumPad0 | ctrl+numpad0 | null | ctrl+[Numpad0] | | | Shift+Numpad0 | --- | Shift+NumPad0 | | Shift+NumPad0 | shift+numpad0 | null | shift+[Numpad0] | | | Ctrl+Shift+Numpad0 | --- | Ctrl+Shift+NumPad0 | | Ctrl+Shift+NumPad0 | ctrl+shift+numpad0 | null | ctrl+shift+[Numpad0] | | -| Alt+Numpad0 | --- | Alt+NumPad0 | | Alt+NumPad0 | alt+numpad0 | null | alt+[Numpad0] | | -| Ctrl+Alt+Numpad0 | --- | Ctrl+Alt+NumPad0 | | Ctrl+Alt+NumPad0 | ctrl+alt+numpad0 | null | ctrl+alt+[Numpad0] | | -| Shift+Alt+Numpad0 | --- | Shift+Alt+NumPad0 | | Shift+Alt+NumPad0 | shift+alt+numpad0 | null | shift+alt+[Numpad0] | | -| Ctrl+Shift+Alt+Numpad0 | --- | Ctrl+Shift+Alt+NumPad0 | | Ctrl+Shift+Alt+NumPad0 | ctrl+shift+alt+numpad0 | null | ctrl+shift+alt+[Numpad0] | | +| Alt+Numpad0 | --- | Alt+NumPad0 | | Option+NumPad0 | alt+numpad0 | null | alt+[Numpad0] | | +| Ctrl+Alt+Numpad0 | --- | Ctrl+Alt+NumPad0 | | Ctrl+Option+NumPad0 | ctrl+alt+numpad0 | null | ctrl+alt+[Numpad0] | | +| Shift+Alt+Numpad0 | --- | Shift+Alt+NumPad0 | | Shift+Option+NumPad0 | shift+alt+numpad0 | null | shift+alt+[Numpad0] | | +| Ctrl+Shift+Alt+Numpad0 | --- | Ctrl+Shift+Alt+NumPad0 | | Ctrl+Shift+Option+NumPad0 | ctrl+shift+alt+numpad0 | null | ctrl+shift+alt+[Numpad0] | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | IntlBackslash | § | | | § | [IntlBackslash] | null | [IntlBackslash] | NO | | Ctrl+IntlBackslash | § | | | Ctrl+§ | ctrl+[IntlBackslash] | null | ctrl+[IntlBackslash] | NO | | Shift+IntlBackslash | ± | | | Shift+§ | shift+[IntlBackslash] | null | shift+[IntlBackslash] | NO | | Ctrl+Shift+IntlBackslash | ± | | | Ctrl+Shift+§ | ctrl+shift+[IntlBackslash] | null | ctrl+shift+[IntlBackslash] | NO | -| Alt+IntlBackslash | § | | | Alt+§ | alt+[IntlBackslash] | null | alt+[IntlBackslash] | NO | -| Ctrl+Alt+IntlBackslash | § | | | Ctrl+Alt+§ | ctrl+alt+[IntlBackslash] | null | ctrl+alt+[IntlBackslash] | NO | -| Shift+Alt+IntlBackslash | ± | | | Shift+Alt+§ | shift+alt+[IntlBackslash] | null | shift+alt+[IntlBackslash] | NO | -| Ctrl+Shift+Alt+IntlBackslash | ± | | | Ctrl+Shift+Alt+§ | ctrl+shift+alt+[IntlBackslash] | null | ctrl+shift+alt+[IntlBackslash] | NO | +| Alt+IntlBackslash | § | | | Option+§ | alt+[IntlBackslash] | null | alt+[IntlBackslash] | NO | +| Ctrl+Alt+IntlBackslash | § | | | Ctrl+Option+§ | ctrl+alt+[IntlBackslash] | null | ctrl+alt+[IntlBackslash] | NO | +| Shift+Alt+IntlBackslash | ± | | | Shift+Option+§ | shift+alt+[IntlBackslash] | null | shift+alt+[IntlBackslash] | NO | +| Ctrl+Shift+Alt+IntlBackslash | ± | | | Ctrl+Shift+Option+§ | ctrl+shift+alt+[IntlBackslash] | null | ctrl+shift+alt+[IntlBackslash] | NO | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | IntlRo | --- | | | null | [IntlRo] | null | [IntlRo] | NO | | Ctrl+IntlRo | --- | | | null | ctrl+[IntlRo] | null | ctrl+[IntlRo] | NO | diff --git a/src/vs/workbench/services/label/common/labelService.ts b/src/vs/workbench/services/label/common/labelService.ts index 15252f0546e..05b4c9776c2 100644 --- a/src/vs/workbench/services/label/common/labelService.ts +++ b/src/vs/workbench/services/label/common/labelService.ts @@ -84,7 +84,7 @@ class ResourceLabelFormattersHandler implements IWorkbenchContribution { constructor(@ILabelService labelService: ILabelService) { resourceLabelFormattersExtPoint.setHandler((extensions, delta) => { delta.added.forEach(added => added.value.forEach(formatter => { - if (!isProposedApiEnabled(added.description) && formatter.formatting.workspaceTooltip) { + if (!isProposedApiEnabled(added.description, 'contribLabelFormatterWorkspaceTooltip') && formatter.formatting.workspaceTooltip) { // workspaceTooltip is only proposed formatter.formatting.workspaceTooltip = undefined; } diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index 96370d9ee74..7800cc1c21f 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -19,7 +19,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import * as nls from 'vs/nls'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { Extensions, getDefaultValue, IConfigurationRegistry, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; +import { Extensions, getDefaultValue, IConfigurationRegistry, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; import { EditorResolution } from 'vs/platform/editor/common/editor'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; @@ -541,7 +541,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return null; } const schema = Registry.as(Extensions.Configuration).getConfigurationProperties()[settingKey]; - const isOverrideProperty = OVERRIDE_PROPERTY_PATTERN.test(settingKey); + const isOverrideProperty = OVERRIDE_PROPERTY_REGEX.test(settingKey); if (!schema && !isOverrideProperty) { return null; } diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index bad2c3faee7..a9026ea6131 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -42,8 +42,8 @@ export interface ISettingsGroup { range: IRange; title: string; titleRange: IRange; - order: number; sections: ISettingsSection[]; + order?: number; extensionInfo?: IConfigurationExtensionInfo; } diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 6b5f95fde2c..8579000624e 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -15,7 +15,7 @@ import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/mod import { ITextEditorModel } from 'vs/editor/common/services/resolverService'; import * as nls from 'vs/nls'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationPropertySchema, IConfigurationRegistry, OVERRIDE_PROPERTY_PATTERN, IConfigurationExtensionInfo } from 'vs/platform/configuration/common/configurationRegistry'; +import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationPropertySchema, IConfigurationRegistry, IConfigurationExtensionInfo, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; @@ -342,7 +342,7 @@ function parse(model: ITextModel, isSettingsProperty: (currentProperty: string, }; if (previousParents.length === settingsPropertyIndex + 1) { settings.push(setting); - if (OVERRIDE_PROPERTY_PATTERN.test(name)) { + if (OVERRIDE_PROPERTY_REGEX.test(name)) { overrideSetting = setting; } } else { @@ -567,7 +567,7 @@ export class DefaultSettings extends Disposable { if (!settingsGroup) { settingsGroup = result.find(g => g.title === title && g.extensionInfo?.id === config.extensionInfo?.id); if (!settingsGroup) { - settingsGroup = { sections: [{ settings: [] }], id: config.id || '', title: title || '', titleRange: nullRange, order: config.order ?? 0, range: nullRange, extensionInfo: config.extensionInfo }; + settingsGroup = { sections: [{ settings: [] }], id: config.id || '', title: title || '', titleRange: nullRange, order: config.order, range: nullRange, extensionInfo: config.extensionInfo }; result.push(settingsGroup); } } else { @@ -576,7 +576,7 @@ export class DefaultSettings extends Disposable { } if (config.properties) { if (!settingsGroup) { - settingsGroup = { sections: [{ settings: [] }], id: config.id || '', title: config.id || '', titleRange: nullRange, order: config.order ?? 0, range: nullRange, extensionInfo: config.extensionInfo }; + settingsGroup = { sections: [{ settings: [] }], id: config.id || '', title: config.id || '', titleRange: nullRange, order: config.order, range: nullRange, extensionInfo: config.extensionInfo }; result.push(settingsGroup); } const configurationSettings: ISetting[] = []; @@ -618,7 +618,7 @@ export class DefaultSettings extends Disposable { description = ''; } const descriptionLines = description.split('\n'); - const overrides = OVERRIDE_PROPERTY_PATTERN.test(key) ? this.parseOverrideSettings(prop.default) : []; + const overrides = OVERRIDE_PROPERTY_REGEX.test(key) ? this.parseOverrideSettings(prop.default) : []; let listItemType: string | undefined; if (prop.type === 'array' && prop.items && !isArray(prop.items) && prop.items.type) { if (prop.items.enum) { @@ -1009,7 +1009,8 @@ class SettingsContentBuilder { setting.descriptionRanges = []; const descriptionPreValue = indent + '// '; - for (let line of (setting.deprecationMessage ? [setting.deprecationMessage, ...setting.description] : setting.description)) { + const deprecationMessageLines = setting.deprecationMessage?.split(/\n/g) ?? []; + for (let line of [...deprecationMessageLines, ...setting.description]) { line = fixSettingLink(line); this._contentByLines.push(descriptionPreValue + line); diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index ce7401d6d33..0957d9529c6 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -9,7 +9,7 @@ import { localize } from 'vs/nls'; import { IDisposable, dispose, DisposableStore, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IProgressService, IProgressOptions, IProgressStep, ProgressLocation, IProgress, Progress, IProgressCompositeOptions, IProgressNotificationOptions, IProgressRunner, IProgressIndicator, IProgressWindowOptions, IProgressDialogOptions } from 'vs/platform/progress/common/progress'; import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/browser/statusbar'; -import { RunOnceScheduler, timeout } from 'vs/base/common/async'; +import { DeferredPromise, RunOnceScheduler, timeout } from 'vs/base/common/async'; import { ProgressBadge, IActivityService } from 'vs/workbench/services/activity/common/activity'; import { INotificationService, Severity, INotificationHandle } from 'vs/platform/notification/common/notification'; import { Action } from 'vs/base/common/actions'; @@ -225,8 +225,7 @@ export class ProgressService extends Disposable implements IProgressService { // Create a promise that we can resolve as needed // when the outside calls dispose on us - let promiseResolve: () => void; - const promise = new Promise(resolve => promiseResolve = resolve); + const promise = new DeferredPromise(); this.withWindowProgress({ location: ProgressLocation.Window, @@ -249,16 +248,16 @@ export class ProgressService extends Disposable implements IProgressService { // Continue to report progress as it happens const onDidReportListener = progressStateModel.onDidReport(step => reportProgress(step)); - promise.finally(() => onDidReportListener.dispose()); + promise.p.finally(() => onDidReportListener.dispose()); // When the progress model gets disposed, we are done as well - Event.once(progressStateModel.onWillDispose)(() => promiseResolve()); + Event.once(progressStateModel.onWillDispose)(() => promise.complete()); - return promise; + return promise.p; }); // Dispose means completing our promise - return toDisposable(() => promiseResolve()); + return toDisposable(() => promise.complete()); }; const createNotification = (message: string, silent: boolean, increment?: number): INotificationHandle => { diff --git a/src/vs/workbench/services/search/common/getFileResults.ts b/src/vs/workbench/services/search/common/getFileResults.ts index 91fe2623dfd..f2e81f913dd 100644 --- a/src/vs/workbench/services/search/common/getFileResults.ts +++ b/src/vs/workbench/services/search/common/getFileResults.ts @@ -25,8 +25,7 @@ export const getFileResults = ( text = new TextDecoder('utf-16be').decode(bytes); } else { text = new TextDecoder('utf8').decode(bytes); - // allow-any-unicode-next-line - if (text.slice(0, 1000).includes('�') && bytes.includes(0)) { + if (text.slice(0, 1000).includes('\uFFFD') && bytes.includes(0)) { return []; } } diff --git a/src/vs/workbench/services/telemetry/electron-sandbox/workbenchCommonProperties.ts b/src/vs/workbench/services/telemetry/electron-sandbox/workbenchCommonProperties.ts index d81230c34f2..ff36a5231e6 100644 --- a/src/vs/workbench/services/telemetry/electron-sandbox/workbenchCommonProperties.ts +++ b/src/vs/workbench/services/telemetry/electron-sandbox/workbenchCommonProperties.ts @@ -5,7 +5,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties'; -import { instanceStorageKey, firstSessionDateStorageKey, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry'; +import { firstSessionDateStorageKey, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry'; import { cleanRemoteAuthority } from 'vs/platform/telemetry/common/telemetryUtils'; import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { IFileService } from 'vs/platform/files/common/files'; @@ -23,7 +23,6 @@ export async function resolveWorkbenchCommonProperties( remoteAuthority?: string ): Promise<{ [name: string]: string | boolean | undefined }> { const result = await resolveCommonProperties(fileService, release, hostname, process.arch, commit, version, machineId, msftInternalDomains, installSourcePath); - const instanceId = storageService.get(instanceStorageKey, StorageScope.GLOBAL)!; const firstSessionDate = storageService.get(firstSessionDateStorageKey, StorageScope.GLOBAL)!; const lastSessionDate = storageService.get(lastSessionDateStorageKey, StorageScope.GLOBAL)!; @@ -37,8 +36,6 @@ export async function resolveWorkbenchCommonProperties( result['common.lastSessionDate'] = lastSessionDate || ''; // __GDPR__COMMON__ "common.isNewSession" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } result['common.isNewSession'] = !lastSessionDate ? '1' : '0'; - // __GDPR__COMMON__ "common.instanceId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - result['common.instanceId'] = instanceId; // __GDPR__COMMON__ "common.remoteAuthority" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } result['common.remoteAuthority'] = cleanRemoteAuthority(remoteAuthority); diff --git a/src/vs/workbench/services/telemetry/test/electron-browser/commonProperties.test.ts b/src/vs/workbench/services/telemetry/test/electron-browser/commonProperties.test.ts index 26d36e27ba0..fed7bd8c4ff 100644 --- a/src/vs/workbench/services/telemetry/test/electron-browser/commonProperties.test.ts +++ b/src/vs/workbench/services/telemetry/test/electron-browser/commonProperties.test.ts @@ -64,7 +64,6 @@ suite('Telemetry - common properties', function () { assert.ok('common.lastSessionDate' in props, 'lastSessionDate'); // conditional, see below, 'lastSessionDate'ow assert.ok('common.isNewSession' in props, 'isNewSession'); // machine id et al - assert.ok('common.instanceId' in props, 'instanceId'); assert.ok('common.machineId' in props, 'machineId'); fs.unlinkSync(installSource); const props_1 = await resolveWorkbenchCommonProperties(testStorageService, testFileService, release(), hostname(), commit, version, 'someMachineId', undefined, installSource); diff --git a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts index f02cbe27ce6..387516cc874 100644 --- a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts +++ b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts @@ -484,7 +484,13 @@ class TMTokenization extends Disposable { } public tokenize2(line: string, state: StackElement): TokenizationResult2 { - let textMateResult = this._grammar.tokenizeLine2(line, state); + const textMateResult = this._grammar.tokenizeLine2(line, state, 500); + + if (textMateResult.stoppedEarly) { + console.warn(`Time limit reached when tokenizing line: ${line.substring(0, 100)}`); + // return the state at the beginning of the line + return new TokenizationResult2(textMateResult.tokens, state); + } if (this._containsEmbeddedLanguages) { let seenLanguages = this._seenLanguages; diff --git a/src/vs/workbench/services/textMate/common/textMateService.ts b/src/vs/workbench/services/textMate/common/textMateService.ts index 278efa9a571..4dcb0e635de 100644 --- a/src/vs/workbench/services/textMate/common/textMateService.ts +++ b/src/vs/workbench/services/textMate/common/textMateService.ts @@ -33,7 +33,7 @@ export interface IGrammar { /** * Tokenize `lineText` using previous line state `prevState`. */ - tokenizeLine(lineText: string, prevState: StackElement | null): ITokenizeLineResult; + tokenizeLine(lineText: string, prevState: StackElement | null, timeLimit?: number): ITokenizeLineResult; /** * Tokenize `lineText` using previous line state `prevState`. * The result contains the tokens in binary format, resolved with the following information: @@ -44,7 +44,7 @@ export interface IGrammar { * - background color * e.g. for getting the languageId: `(metadata & MetadataConsts.LANGUAGEID_MASK) >>> MetadataConsts.LANGUAGEID_OFFSET` */ - tokenizeLine2(lineText: string, prevState: StackElement | null): ITokenizeLineResult2; + tokenizeLine2(lineText: string, prevState: StackElement | null, timeLimit?: number): ITokenizeLineResult2; } export interface ITokenizeLineResult { readonly tokens: IToken[]; @@ -52,6 +52,10 @@ export interface ITokenizeLineResult { * The `prevState` to be passed on to the next line tokenization. */ readonly ruleStack: StackElement; + /** + * Did tokenization stop early due to reaching the time limit. + */ + readonly stoppedEarly: boolean; } /** * Helpers to manage the "collapsed" metadata of an entire StackElement stack. @@ -97,6 +101,10 @@ export interface ITokenizeLineResult2 { * The `prevState` to be passed on to the next line tokenization. */ readonly ruleStack: StackElement; + /** + * Did tokenization stop early due to reaching the time limit. + */ + readonly stoppedEarly: boolean; } export interface IToken { startIndex: number; diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index 8cbe7940658..13a0a850882 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -678,8 +678,8 @@ function toCSSSelector(extensionId: string, path: string) { let str = `${extensionId}-${path}`; //remove all characters that are not allowed in css - str = str.replace(/[^_\-a-zA-Z0-9]/g, '-'); - if (str.charAt(0).match(/[0-9\-]/)) { + str = str.replace(/[^_a-zA-Z0-9-]/g, '-'); + if (str.charAt(0).match(/[0-9-]/)) { str = '_' + str; } return str; diff --git a/src/vs/workbench/services/themes/common/iconExtensionPoint.ts b/src/vs/workbench/services/themes/common/iconExtensionPoint.ts index cfee6d8370b..b7d60683e0b 100644 --- a/src/vs/workbench/services/themes/common/iconExtensionPoint.ts +++ b/src/vs/workbench/services/themes/common/iconExtensionPoint.ts @@ -126,8 +126,8 @@ export class IconExtensionPoint { const extensionValue = extension.value; const collector = extension.collector; - if (!isProposedApiEnabled(extension.description)) { - collector.error(nls.localize('invalid.icons.proposedAPI', "'configuration.icons is a proposed contribution point and only available when running out of dev or with the following command line switch: --enable-proposed-api {0}", extension.description.identifier.value)); + if (!isProposedApiEnabled(extension.description, 'contribIcons')) { + collector.error(nls.localize('invalid.icons.proposedAPI', "'configuration.icons is a proposed contribution point. It requires 'package.json#enabledApiProposals: [\"contribIcons\"]' and is only available when running out of dev or with the following command line switch: --enable-proposed-api {0}", extension.description.identifier.value)); return; } @@ -180,8 +180,8 @@ export class IconFontExtensionPoint { const extensionValue = extension.value; const collector = extension.collector; - if (!isProposedApiEnabled(extension.description)) { - collector.error(nls.localize('invalid.iconFonts.proposedAPI', "'configuration.iconFonts is a proposed contribution point and only available when running out of dev or with the following command line switch: --enable-proposed-api {0}", extension.description.identifier.value)); + if (!isProposedApiEnabled(extension.description, 'contribIconFonts')) { + collector.error(nls.localize('invalid.iconFonts.proposedAPI', "'configuration.iconFonts is a proposed contribution point. It requires 'package.json#enabledApiProposals: [\"contribIconFonts\"]' and is and only available when running out of dev or with the following command line switch: --enable-proposed-api {0}", extension.description.identifier.value)); return; } diff --git a/src/vs/workbench/services/themes/common/productIconThemeSchema.ts b/src/vs/workbench/services/themes/common/productIconThemeSchema.ts index 5b77311e497..fa85dec3cdc 100644 --- a/src/vs/workbench/services/themes/common/productIconThemeSchema.ts +++ b/src/vs/workbench/services/themes/common/productIconThemeSchema.ts @@ -9,10 +9,10 @@ import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/plat import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { iconsSchemaId } from 'vs/platform/theme/common/iconRegistry'; -export const fontIdRegex = '^([\\w-_]+)$'; +export const fontIdRegex = '^([\\w_-]+)$'; export const fontStyleRegex = '^(normal|italic|(oblique[ \\w\\s-]+))$'; export const fontWeightRegex = '^(normal|bold|lighter|bolder|(\\d{0-1000}))$'; -export const fontSizeRegex = '^([\\w .%-_]+)$'; +export const fontSizeRegex = '^([\\w .%_-]+)$'; const schemaId = 'vscode://schemas/product-icon-theme'; const schema: IJSONSchema = { diff --git a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts index 141d2420fa2..c4b4ea584ef 100644 --- a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts @@ -201,12 +201,12 @@ suite('ExtHostLanguageFeatureCommands', function () { return rpcProtocol.sync().then(() => { return commands.executeCommand('vscode.executeWorkspaceSymbolProvider', 'testing').then(value => { + assert.strictEqual(value.length, 2); // de-duped for (let info of value) { assert.strictEqual(info instanceof types.SymbolInformation, true); assert.strictEqual(info.name, 'testing'); assert.strictEqual(info.kind, types.SymbolKind.Array); } - assert.strictEqual(value.length, 3); }); }); }); @@ -1366,6 +1366,28 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.strictEqual(outgoing[0].to.name, 'OUTGOING'); }); + test('prepareCallHierarchy throws TypeError if clangd returns empty result #137415', async function () { + + disposables.push(extHost.registerCallHierarchyProvider(nullExtensionDescription, defaultSelector, new class implements vscode.CallHierarchyProvider { + prepareCallHierarchy(document: vscode.TextDocument, position: vscode.Position,): vscode.ProviderResult { + return []; + } + provideCallHierarchyIncomingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): vscode.ProviderResult { + return []; + } + provideCallHierarchyOutgoingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): vscode.ProviderResult { + return []; + } + })); + + await rpcProtocol.sync(); + + const root = await commands.executeCommand('vscode.prepareCallHierarchy', model.uri, new types.Position(0, 0)); + + assert.ok(Array.isArray(root)); + assert.strictEqual(root.length, 0); + }); + // --- type hierarchy test('TypeHierarchy, back and forth', async function () { diff --git a/src/vs/workbench/test/browser/api/extHostAuthentication.test.ts b/src/vs/workbench/test/browser/api/extHostAuthentication.test.ts index 87b04169218..aa5e63baa4d 100644 --- a/src/vs/workbench/test/browser/api/extHostAuthentication.test.ts +++ b/src/vs/workbench/test/browser/api/extHostAuthentication.test.ts @@ -7,7 +7,6 @@ import * as assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; -import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; @@ -20,7 +19,7 @@ import { ExtHostContext, MainContext } from 'vs/workbench/api/common/extHost.pro import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication'; import { IActivityService } from 'vs/workbench/services/activity/common/activity'; import { AuthenticationService, IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; -import { IExtensionService, nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionService, nullExtensionDescription as extensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { TestRemoteAgentService } from 'vs/workbench/services/remote/test/common/testServices'; import { TestRPCProtocol } from 'vs/workbench/test/browser/api/testRPCProtocol'; @@ -89,7 +88,6 @@ class TestAuthProvider implements AuthenticationProvider { suite('ExtHostAuthentication', () => { let disposables: DisposableStore; - let extensionDescription: IExtensionDescription = { ...nullExtensionDescription, enableProposedApi: true }; let extHostAuthentication: ExtHostAuthentication; let instantiationService: TestInstantiationService; diff --git a/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts index 0871f46573c..52f13c584d7 100644 --- a/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts @@ -681,9 +681,41 @@ suite('ExtHostLanguageFeatures', function () { let value = await getWorkspaceSymbols(''); assert.strictEqual(value.length, 1); const [first] = value; - const [, symbols] = first; - assert.strictEqual(symbols.length, 1); - assert.strictEqual(symbols[0].name, 'testing'); + assert.strictEqual(first.symbol.name, 'testing'); + }); + + test('Navigate types, de-duplicate results', async () => { + const uri = URI.from({ scheme: 'foo', path: '/some/path' }); + disposables.push(extHost.registerWorkspaceSymbolProvider(defaultExtension, new class implements vscode.WorkspaceSymbolProvider { + provideWorkspaceSymbols(): any { + return [new types.SymbolInformation('ONE', types.SymbolKind.Array, undefined, new types.Location(uri, new types.Range(0, 0, 1, 1)))]; + } + })); + + disposables.push(extHost.registerWorkspaceSymbolProvider(defaultExtension, new class implements vscode.WorkspaceSymbolProvider { + provideWorkspaceSymbols(): any { + return [new types.SymbolInformation('ONE', types.SymbolKind.Array, undefined, new types.Location(uri, new types.Range(0, 0, 1, 1)))]; // get de-duped + } + })); + + disposables.push(extHost.registerWorkspaceSymbolProvider(defaultExtension, new class implements vscode.WorkspaceSymbolProvider { + provideWorkspaceSymbols(): any { + return [new types.SymbolInformation('ONE', types.SymbolKind.Array, undefined, new types.Location(uri, undefined!))]; // NO dedupe because of resolve + } + resolveWorkspaceSymbol(a: vscode.SymbolInformation) { + return a; + } + })); + + disposables.push(extHost.registerWorkspaceSymbolProvider(defaultExtension, new class implements vscode.WorkspaceSymbolProvider { + provideWorkspaceSymbols(): any { + return [new types.SymbolInformation('ONE', types.SymbolKind.Struct, undefined, new types.Location(uri, new types.Range(0, 0, 1, 1)))]; // NO dedupe because of kind + } + })); + + await rpcProtocol.sync(); + let value = await getWorkspaceSymbols(''); + assert.strictEqual(value.length, 3); }); // --- rename diff --git a/src/vs/workbench/test/browser/api/extHostTreeViews.test.ts b/src/vs/workbench/test/browser/api/extHostTreeViews.test.ts index 46fc654a936..db66293a7e9 100644 --- a/src/vs/workbench/test/browser/api/extHostTreeViews.test.ts +++ b/src/vs/workbench/test/browser/api/extHostTreeViews.test.ts @@ -17,9 +17,8 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { mock } from 'vs/base/test/common/mock'; import { TreeItemCollapsibleState, ITreeItem, IRevealOptions } from 'vs/workbench/common/views'; import { NullLogService } from 'vs/platform/log/common/log'; -import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import type { IDisposable } from 'vs/base/common/lifecycle'; -import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { nullExtensionDescription as extensionsDescription } from 'vs/workbench/services/extensions/common/extensions'; suite('ExtHostTreeView', function () { @@ -42,8 +41,6 @@ suite('ExtHostTreeView', function () { } - const extensionsDescription: IExtensionDescription = { ...nullExtensionDescription, enableProposedApi: true }; - let testObject: ExtHostTreeViews; let target: RecordingShape; let onDidChangeTreeNode: Emitter<{ key: string } | undefined>; diff --git a/src/vs/workbench/test/browser/api/mainThreadTreeViews.test.ts b/src/vs/workbench/test/browser/api/mainThreadTreeViews.test.ts index a18928327b3..9f72491c290 100644 --- a/src/vs/workbench/test/browser/api/mainThreadTreeViews.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadTreeViews.test.ts @@ -73,7 +73,7 @@ suite('MainThreadHostTreeView', function () { } drain(): any { return null; } }, new TestViewsService(), new TestNotificationService(), testExtensionService, new NullLogService()); - mainThreadTreeViews.$registerTreeViewDataProvider(testTreeViewId, { showCollapseAll: false, canSelectMany: false, canDragAndDrop: false }); + mainThreadTreeViews.$registerTreeViewDataProvider(testTreeViewId, { showCollapseAll: false, canSelectMany: false, dragAndDropMimeTypes: [] }); await testExtensionService.whenInstalledExtensionsRegistered(); }); diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadWorkspace.test.ts b/src/vs/workbench/test/browser/api/mainThreadWorkspace.test.ts similarity index 96% rename from src/vs/workbench/test/electron-browser/api/mainThreadWorkspace.test.ts rename to src/vs/workbench/test/browser/api/mainThreadWorkspace.test.ts index ef17845c3b9..39d82eb14b0 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadWorkspace.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadWorkspace.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { workbenchInstantiationService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; +import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ISearchService, IFileQuery } from 'vs/workbench/services/search/common/search'; import { MainThreadWorkspace } from 'vs/workbench/api/browser/mainThreadWorkspace'; @@ -22,7 +22,7 @@ suite('MainThreadWorkspace', () => { setup(() => { disposables = new DisposableStore(); - instantiationService = workbenchInstantiationService(disposables) as TestInstantiationService; + instantiationService = workbenchInstantiationService(undefined, disposables) as TestInstantiationService; configService = instantiationService.get(IConfigurationService) as TestConfigurationService; configService.setUserConfiguration('search', {}); diff --git a/src/vs/workbench/test/browser/quickAccess.test.ts b/src/vs/workbench/test/browser/quickAccess.test.ts index 3130a990ac5..587a2fa9bac 100644 --- a/src/vs/workbench/test/browser/quickAccess.test.ts +++ b/src/vs/workbench/test/browser/quickAccess.test.ts @@ -66,7 +66,8 @@ suite('QuickAccess', () => { provide(picker: IQuickPick, token: CancellationToken): IDisposable { assert.ok(picker); provider2Called = true; - token.onCancellationRequested(() => provider2Canceled = true); + token.onCancellationRequested(() => + provider2Canceled = true); return toDisposable(() => provider2Disposed = true); } diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 19b665fb82c..4e68ef3f427 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -122,10 +122,10 @@ import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEd import { IEnterWorkspaceResult, IRecent, IRecentlyOpened, IWorkspaceFolderCreationData, IWorkspaceIdentifier, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; import { TestWorkspaceTrustManagementService, TestWorkspaceTrustRequestService } from 'vs/workbench/services/workspaces/test/common/testWorkspaceTrustService'; -import { IShellLaunchConfig, ITerminalProfile, TerminalLocation, TerminalShellType } from 'vs/platform/terminal/common/terminal'; -import { ICreateTerminalOptions, ITerminalInstance, ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { IExtensionTerminalProfile, IShellLaunchConfig, ITerminalProfile, TerminalLocation, TerminalShellType } from 'vs/platform/terminal/common/terminal'; +import { ICreateTerminalOptions, ITerminalEditorService, ITerminalGroup, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, TerminalEditorLocation } from 'vs/workbench/contrib/terminal/browser/terminal'; import { assertIsDefined, isArray } from 'vs/base/common/types'; -import { IShellLaunchConfigResolveOptions, ITerminalBackend, ITerminalProfileResolverService } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IRegisterContributedProfileArgs, IShellLaunchConfigResolveOptions, ITerminalBackend, ITerminalProfileProvider, ITerminalProfileResolverService, ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal'; import { EditorResolverService } from 'vs/workbench/services/editor/browser/editorResolverService'; import { FILE_EDITOR_INPUT_ID } from 'vs/workbench/contrib/files/common/files'; import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService'; @@ -141,6 +141,9 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b import { IPaneCompositePart, IPaneCompositeSelectorPart } from 'vs/workbench/browser/parts/paneCompositePart'; import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; +import { FindReplaceState } from 'vs/editor/contrib/find/findState'; +import { TerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorInput'; +import { DeserializedTerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorSerializer'; export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined, undefined, undefined, undefined, undefined); @@ -1723,15 +1726,94 @@ export class TestTerminalInstanceService implements ITerminalInstanceService { onDidCreateInstance = Event.None; declare readonly _serviceBrand: undefined; - async getXtermConstructor(): Promise { throw new Error('Method not implemented.'); } - async getXtermSearchConstructor(): Promise { throw new Error('Method not implemented.'); } - async getXtermUnicode11Constructor(): Promise { throw new Error('Method not implemented.'); } - async getXtermWebglConstructor(): Promise { throw new Error('Method not implemented.'); } + convertProfileToShellLaunchConfig(shellLaunchConfigOrProfile?: IShellLaunchConfig | ITerminalProfile, cwd?: string | URI): IShellLaunchConfig { throw new Error('Method not implemented.'); } preparePathForTerminalAsync(path: string, executable: string | undefined, title: string, shellType: TerminalShellType, remoteAuthority: string | undefined): Promise { throw new Error('Method not implemented.'); } createInstance(options: ICreateTerminalOptions, target?: TerminalLocation): ITerminalInstance { throw new Error('Method not implemented.'); } getBackend(remoteAuthority?: string): ITerminalBackend | undefined { throw new Error('Method not implemented.'); } } +export class TestTerminalEditorService implements ITerminalEditorService { + _serviceBrand: undefined; + activeInstance: ITerminalInstance | undefined; + instances: readonly ITerminalInstance[] = []; + onDidDisposeInstance = Event.None; + onDidFocusInstance = Event.None; + onDidChangeActiveInstance = Event.None; + onDidChangeInstances = Event.None; + openEditor(instance: ITerminalInstance, editorOptions?: TerminalEditorLocation): Promise { throw new Error('Method not implemented.'); } + detachActiveEditorInstance(): ITerminalInstance { throw new Error('Method not implemented.'); } + detachInstance(instance: ITerminalInstance): void { throw new Error('Method not implemented.'); } + splitInstance(instanceToSplit: ITerminalInstance, shellLaunchConfig?: IShellLaunchConfig): ITerminalInstance { throw new Error('Method not implemented.'); } + revealActiveEditor(preserveFocus?: boolean): void { throw new Error('Method not implemented.'); } + resolveResource(instance: ITerminalInstance | URI): URI { throw new Error('Method not implemented.'); } + reviveInput(deserializedInput: DeserializedTerminalEditorInput): TerminalEditorInput { throw new Error('Method not implemented.'); } + getInputFromResource(resource: URI): TerminalEditorInput { throw new Error('Method not implemented.'); } + setActiveInstance(instance: ITerminalInstance): void { throw new Error('Method not implemented.'); } + getInstanceFromResource(resource: URI | undefined): ITerminalInstance | undefined { throw new Error('Method not implemented.'); } + focusFindWidget(): void { throw new Error('Method not implemented.'); } + hideFindWidget(): void { throw new Error('Method not implemented.'); } + getFindState(): FindReplaceState { throw new Error('Method not implemented.'); } + findNext(): void { throw new Error('Method not implemented.'); } + findPrevious(): void { throw new Error('Method not implemented.'); } +} + +export class TestTerminalGroupService implements ITerminalGroupService { + _serviceBrand: undefined; + activeInstance: ITerminalInstance | undefined; + instances: readonly ITerminalInstance[] = []; + groups: readonly ITerminalGroup[] = []; + activeGroup: ITerminalGroup | undefined; + activeGroupIndex: number = 0; + onDidChangeActiveGroup = Event.None; + onDidDisposeGroup = Event.None; + onDidChangeGroups = Event.None; + onDidChangePanelOrientation = Event.None; + onDidDisposeInstance = Event.None; + onDidFocusInstance = Event.None; + onDidChangeActiveInstance = Event.None; + onDidChangeInstances = Event.None; + createGroup(instance?: any): ITerminalGroup { throw new Error('Method not implemented.'); } + getGroupForInstance(instance: ITerminalInstance): ITerminalGroup | undefined { throw new Error('Method not implemented.'); } + moveGroup(source: ITerminalInstance, target: ITerminalInstance): void { throw new Error('Method not implemented.'); } + moveGroupToEnd(source: ITerminalInstance): void { throw new Error('Method not implemented.'); } + moveInstance(source: ITerminalInstance, target: ITerminalInstance, side: 'before' | 'after'): void { throw new Error('Method not implemented.'); } + unsplitInstance(instance: ITerminalInstance): void { throw new Error('Method not implemented.'); } + joinInstances(instances: ITerminalInstance[]): void { throw new Error('Method not implemented.'); } + instanceIsSplit(instance: ITerminalInstance): boolean { throw new Error('Method not implemented.'); } + getGroupLabels(): string[] { throw new Error('Method not implemented.'); } + setActiveGroupByIndex(index: number): void { throw new Error('Method not implemented.'); } + setActiveGroupToNext(): void { throw new Error('Method not implemented.'); } + setActiveGroupToPrevious(): void { throw new Error('Method not implemented.'); } + setActiveInstanceByIndex(terminalIndex: number): void { throw new Error('Method not implemented.'); } + setContainer(container: HTMLElement): void { throw new Error('Method not implemented.'); } + showPanel(focus?: boolean): Promise { throw new Error('Method not implemented.'); } + hidePanel(): void { throw new Error('Method not implemented.'); } + focusTabs(): void { throw new Error('Method not implemented.'); } + showTabs(): void { throw new Error('Method not implemented.'); } + setActiveInstance(instance: ITerminalInstance): void { throw new Error('Method not implemented.'); } + getInstanceFromResource(resource: URI | undefined): ITerminalInstance | undefined { throw new Error('Method not implemented.'); } + focusFindWidget(): void { throw new Error('Method not implemented.'); } + hideFindWidget(): void { throw new Error('Method not implemented.'); } + getFindState(): FindReplaceState { throw new Error('Method not implemented.'); } + findNext(): void { throw new Error('Method not implemented.'); } + findPrevious(): void { throw new Error('Method not implemented.'); } +} + +export class TestTerminalProfileService implements ITerminalProfileService { + _serviceBrand: undefined; + availableProfiles: ITerminalProfile[] = []; + contributedProfiles: IExtensionTerminalProfile[] = []; + profilesReady: Promise = Promise.resolve(); + onDidChangeAvailableProfiles = Event.None; + getPlatformKey(): Promise { throw new Error('Method not implemented.'); } + refreshAvailableProfiles(): void { throw new Error('Method not implemented.'); } + getDefaultProfileName(): string | undefined { throw new Error('Method not implemented.'); } + getContributedDefaultProfile(shellLaunchConfig: IShellLaunchConfig): Promise { throw new Error('Method not implemented.'); } + registerContributedProfile(args: IRegisterContributedProfileArgs): Promise { throw new Error('Method not implemented.'); } + getContributedProfileProvider(extensionIdentifier: string, id: string): ITerminalProfileProvider | undefined { throw new Error('Method not implemented.'); } + registerTerminalProfileProvider(extensionIdentifier: string, id: string, profileProvider: ITerminalProfileProvider): IDisposable { throw new Error('Method not implemented.'); } +} + export class TestTerminalProfileResolverService implements ITerminalProfileResolverService { _serviceBrand: undefined; defaultProfileName = ''; diff --git a/src/vs/workbench/test/electron-browser/colorRegistry.releaseTest.ts b/src/vs/workbench/test/electron-browser/colorRegistry.releaseTest.ts index eff9650b2cc..f176e90a9b8 100644 --- a/src/vs/workbench/test/electron-browser/colorRegistry.releaseTest.ts +++ b/src/vs/workbench/test/electron-browser/colorRegistry.releaseTest.ts @@ -40,7 +40,7 @@ suite('Color Registry', function () { const reqContext = await new RequestService(new TestConfigurationService(), environmentService, new NullLogService()).request({ url: 'https://raw.githubusercontent.com/microsoft/vscode-docs/vnext/api/references/theme-color.md' }, CancellationToken.None); const content = (await asText(reqContext))!; - const expression = /\-\s*\`([\w\.]+)\`: (.*)/g; + const expression = /-\s*\`([\w\.]+)\`: (.*)/g; let m: RegExpExecArray | null; let colorsInDoc: { [id: string]: ColorInfo } = Object.create(null); diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 39cf934c361..dd1dd6a6864 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -122,7 +122,10 @@ import { OpenerService } from 'vs/editor/browser/services/openerService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; import { ExtensionsStorageSyncService, IExtensionsStorageSyncService } from 'vs/platform/userDataSync/common/extensionsStorageSync'; +import { IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; +registerSingleton(IUserDataSyncLogService, UserDataSyncLogService); registerSingleton(IIgnoredExtensionsManagementService, IgnoredExtensionsManagementService); registerSingleton(IGlobalExtensionEnablementService, GlobalExtensionEnablementService); registerSingleton(IExtensionsStorageSyncService, ExtensionsStorageSyncService); diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index a7516917032..d619ce20089 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -19,6 +19,7 @@ import { mark } from 'vs/base/common/performance'; import { ICredentialsProvider } from 'vs/workbench/services/credentials/common/credentials'; import { TunnelProviderFeatures } from 'vs/platform/remote/common/tunnel'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; +import { DeferredPromise } from 'vs/base/common/async'; interface IResourceUriProvider { (uri: URI): URI; @@ -363,13 +364,6 @@ interface IWorkbenchConstructionOptions { */ readonly webWorkerExtensionHostIframeSrc?: string; - /** - * [TEMPORARY]: This will be removed soon. - * Use an unique origin for the web worker extension host. - * Defaults to true. - */ - readonly __uniqueWebWorkerExtensionHostOrigin?: boolean; - /** * A factory for web sockets. */ @@ -624,8 +618,7 @@ interface IWorkbench { * @param options for setting up the workbench */ let created = false; -let workbenchPromiseResolve: Function; -const workbenchPromise = new Promise(resolve => workbenchPromiseResolve = resolve); +const workbenchPromise = new DeferredPromise(); function create(domElement: HTMLElement, options: IWorkbenchConstructionOptions): IDisposable { // Mark start of workbench @@ -661,14 +654,14 @@ function create(domElement: HTMLElement, options: IWorkbenchConstructionOptions) let instantiatedWorkbench: IWorkbench | undefined = undefined; main(domElement, options).then(workbench => { instantiatedWorkbench = workbench; - workbenchPromiseResolve(workbench); + workbenchPromise.complete(workbench); }); return toDisposable(() => { if (instantiatedWorkbench) { instantiatedWorkbench.shutdown(); } else { - workbenchPromise.then(instantiatedWorkbench => instantiatedWorkbench.shutdown()); + workbenchPromise.p.then(instantiatedWorkbench => instantiatedWorkbench.shutdown()); } }); } @@ -686,7 +679,7 @@ namespace commands { * @return A promise that resolves to the returned value of the given command. */ export async function executeCommand(command: string, ...args: any[]): Promise { - const workbench = await workbenchPromise; + const workbench = await workbenchPromise.p; return workbench.commands.executeCommand(command, ...args); } @@ -706,7 +699,7 @@ namespace env { * @returns A promise that resolves to tuples of source and marks. */ export async function retrievePerformanceMarks(): Promise<[string, readonly IPerformanceMark[]][]> { - const workbench = await workbenchPromise; + const workbench = await workbenchPromise.p; return workbench.env.retrievePerformanceMarks(); } @@ -716,7 +709,7 @@ namespace env { * experience via protocol handler. */ export async function getUriScheme(): Promise { - const workbench = await workbenchPromise; + const workbench = await workbenchPromise.p; return workbench.env.uriScheme; } @@ -726,7 +719,7 @@ namespace env { * workbench. */ export async function openUri(target: URI): Promise { - const workbench = await workbenchPromise; + const workbench = await workbenchPromise.p; return workbench.env.openUri(target); } diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index a595a059d78..ea8d00c999b 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -72,8 +72,7 @@ import { ExtensionManagementService } from 'vs/workbench/services/extensionManag import { ILoggerService } from 'vs/platform/log/common/log'; import { FileLoggerService } from 'vs/platform/log/common/fileLog'; import { UserDataSyncMachinesService, IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; -import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataSyncLogService, IUserDataAutoSyncService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; -import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; +import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataAutoSyncService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; @@ -94,7 +93,6 @@ registerSingleton(IWorkbenchExtensionManagementService, ExtensionManagementServi registerSingleton(IAccessibilityService, AccessibilityService, true); registerSingleton(IContextMenuService, ContextMenuService); registerSingleton(ILoggerService, FileLoggerService); -registerSingleton(IUserDataSyncLogService, UserDataSyncLogService); registerSingleton(IUserDataSyncStoreService, UserDataSyncStoreService); registerSingleton(IUserDataSyncMachinesService, UserDataSyncMachinesService); registerSingleton(IUserDataSyncBackupStoreService, UserDataSyncBackupStoreService); diff --git a/src/vscode-dts/README.md b/src/vscode-dts/README.md new file mode 100644 index 00000000000..86de5ab0a0e --- /dev/null +++ b/src/vscode-dts/README.md @@ -0,0 +1,20 @@ + +## vscode-dts + +This is the place for the stable API and for API proposals. + + +### Consume a proposal + +1. find a proposal you are interested in +1. add its name to your extensions `package.json#enabledApiProposals` property +1. run `npx vscode-dts dev` to download the `d.ts` files into your project +1. don't forget that extension using proposed API cannot be published +1. learn more here: https://code.visualstudio.com/api/advanced-topics/using-proposed-api + +### Add a new proposal + +1. create a _new_ file in this directory, its name must follow this pattern `vscode.proposed.[a-zA-Z]+.d.ts` +1. creating the proposal-file will automatically update `src/vs/workbench/services/extensions/common/extensionsApiProposals.ts` (make sure to run `yarn watch`) +1. declare and implement your proposal +1. make sure to use the `checkProposedApiEnabled` and/or `isProposedApiEnabled`-utils to enforce the API being proposed. Make sure to invoke them with your proposal's name which got generated into `extensionsApiProposals.ts` diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 84b27f81c62..ff186b0103c 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -8598,10 +8598,10 @@ declare module 'vscode' { * * @param command Identifier of the command to execute. * @param rest Parameters passed to the command function. - * @return A thenable that resolves to the returned value of the given command. `undefined` when + * @return A thenable that resolves to the returned value of the given command. Returns `undefined` when * the command handler function doesn't return anything. */ - export function executeCommand(command: string, ...rest: any[]): Thenable; + export function executeCommand(command: string, ...rest: any[]): Thenable; /** * Retrieve the list of all available commands. Commands starting with an underscore are @@ -10784,6 +10784,11 @@ declare module 'vscode' { /** * An event that is emitted when a workspace folder is added or removed. + * + * **Note:** this event will not fire if the first workspace folder is added, removed or changed, + * because in that case the currently executing extensions (including the one that listens to this + * event) will be terminated and restarted so that the (deprecated) `rootPath` property is updated + * to point to the first workspace folder. */ export const onDidChangeWorkspaceFolders: Event; @@ -13914,6 +13919,15 @@ declare module 'vscode' { */ createIfNone?: boolean; + /** + * Whether we should attempt to reauthenticate even if there is already a session available. + * + * If true, a modal dialog will be shown asking the user to sign in again. This is mostly used for scenarios + * where the token needs to be re minted because it has lost some authorization. + * + * Defaults to false. + */ + forceNewSession?: boolean | { detail: string }; /** * Whether we should show the indication to sign in in the Accounts menu. @@ -14065,6 +14079,21 @@ declare module 'vscode' { */ export function getSession(providerId: string, scopes: readonly string[], options?: AuthenticationGetSessionOptions): Thenable; + /** + * Get an authentication session matching the desired scopes. Rejects if a provider with providerId is not + * registered, or if the user does not consent to sharing authentication information with + * the extension. If there are multiple sessions with the same scopes, the user will be shown a + * quickpick to select which account they would like to use. + * + * Currently, there are only two authentication providers that are contributed from built in extensions + * to the editor that implement GitHub and Microsoft authentication: their providerId's are 'github' and 'microsoft'. + * @param providerId The id of the provider to use + * @param scopes A list of scopes representing the permissions requested. These are dependent on the authentication provider + * @param options The {@link AuthenticationGetSessionOptions} to use + * @returns A thenable that resolves to an authentication session + */ + export function getSession(providerId: string, scopes: readonly string[], options: AuthenticationGetSessionOptions & { forceNewSession: true | { detail: string } }): Thenable; + /** * An {@link Event} which fires when the authentication sessions of an authentication provider have * been added, removed, or changed. diff --git a/src/vscode-dts/vscode.proposed.authSession.d.ts b/src/vscode-dts/vscode.proposed.authSession.d.ts new file mode 100644 index 00000000000..5287a5e97c6 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.authSession.d.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + export namespace authentication { + /** + * @deprecated Use {@link getSession()} {@link AuthenticationGetSessionOptions.silent} instead. + */ + export function hasSession(providerId: string, scopes: readonly string[]): Thenable; + } +} diff --git a/src/vscode-dts/vscode.proposed.contribIconFonts.d.ts b/src/vscode-dts/vscode.proposed.contribIconFonts.d.ts new file mode 100644 index 00000000000..dbfa93e4200 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribIconFonts.d.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder declaration for the `iconFonts`-contribution point diff --git a/src/vscode-dts/vscode.proposed.contribIcons.d.ts b/src/vscode-dts/vscode.proposed.contribIcons.d.ts new file mode 100644 index 00000000000..04f4fdc41e0 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribIcons.d.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder declaration for the `icons`-contribution point diff --git a/src/vscode-dts/vscode.proposed.contribLabelFormatterWorkspaceTooltip.d.ts b/src/vscode-dts/vscode.proposed.contribLabelFormatterWorkspaceTooltip.d.ts new file mode 100644 index 00000000000..90814eb2211 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribLabelFormatterWorkspaceTooltip.d.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder declaration for the `workspaceTooltip`-property of the `resourceLabelFormatters` contribution poain diff --git a/src/vscode-dts/vscode.proposed.contribMenuBarHome.d.ts b/src/vscode-dts/vscode.proposed.contribMenuBarHome.d.ts new file mode 100644 index 00000000000..caa0ff228da --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribMenuBarHome.d.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder declaration for the `menuBar/home` menu diff --git a/src/vscode-dts/vscode.proposed.contribRemoteHelp.d.ts b/src/vscode-dts/vscode.proposed.contribRemoteHelp.d.ts new file mode 100644 index 00000000000..f577e0daa2e --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribRemoteHelp.d.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder declaration for the `remoteHelp`-contribution point diff --git a/src/vscode-dts/vscode.proposed.contribViewsRemote.d.ts b/src/vscode-dts/vscode.proposed.contribViewsRemote.d.ts new file mode 100644 index 00000000000..b1e6ca3d77b --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribViewsRemote.d.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder declaration for the `remote`-property of the `views`-contribution diff --git a/src/vscode-dts/vscode.proposed.contribViewsWelcome.d.ts b/src/vscode-dts/vscode.proposed.contribViewsWelcome.d.ts new file mode 100644 index 00000000000..616694ebb7d --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribViewsWelcome.d.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder declaration for the `viewsWelcome`-contribution point diff --git a/src/vscode-dts/vscode.proposed.customEditorMove.d.ts b/src/vscode-dts/vscode.proposed.customEditorMove.d.ts new file mode 100644 index 00000000000..f988913ea61 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.customEditorMove.d.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/86146 + + // TODO: Also for custom editor + + export interface CustomTextEditorProvider { + + /** + * Handle when the underlying resource for a custom editor is renamed. + * + * This allows the webview for the editor be preserved throughout the rename. If this method is not implemented, + * the editor will destroy the previous custom editor and create a replacement one. + * + * @param newDocument New text document to use for the custom editor. + * @param existingWebviewPanel Webview panel for the custom editor. + * @param token A cancellation token that indicates the result is no longer needed. + * + * @return Thenable indicating that the webview editor has been moved. + */ + // eslint-disable-next-line vscode-dts-provider-naming + moveCustomTextEditor?(newDocument: TextDocument, existingWebviewPanel: WebviewPanel, token: CancellationToken): Thenable; + } +} diff --git a/src/vscode-dts/vscode.proposed.d.ts b/src/vscode-dts/vscode.proposed.d.ts deleted file mode 100644 index 8c8510cfa24..00000000000 --- a/src/vscode-dts/vscode.proposed.d.ts +++ /dev/null @@ -1,2711 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/** - * This is the place for API experiments and proposals. - * These API are NOT stable and subject to change. They are only available in the Insiders - * distribution and CANNOT be used in published extensions. - * - * To test these API in local environment: - * - Use Insiders release of 'VS Code'. - * - Add `"enableProposedApi": true` to your package.json. - * - Copy this file to your project. - */ - -declare module 'vscode' { - - //#region resolvers: @alexdima - - export interface MessageOptions { - /** - * Do not render a native message box. - */ - useCustom?: boolean; - } - - export interface RemoteAuthorityResolverContext { - resolveAttempt: number; - } - - export class ResolvedAuthority { - readonly host: string; - readonly port: number; - readonly connectionToken: string | undefined; - - constructor(host: string, port: number, connectionToken?: string); - } - - export interface ResolvedOptions { - extensionHostEnv?: { [key: string]: string | null; }; - - isTrusted?: boolean; - } - - export interface TunnelPrivacy { - themeIcon: string; - id: string; - label: string; - } - - export interface TunnelOptions { - remoteAddress: { port: number, host: string; }; - // The desired local port. If this port can't be used, then another will be chosen. - localAddressPort?: number; - label?: string; - /** - * @deprecated Use privacy instead - */ - public?: boolean; - privacy?: string; - protocol?: string; - } - - export interface TunnelDescription { - remoteAddress: { port: number, host: string; }; - //The complete local address(ex. localhost:1234) - localAddress: { port: number, host: string; } | string; - /** - * @deprecated Use privacy instead - */ - public?: boolean; - privacy?: string; - // If protocol is not provided it is assumed to be http, regardless of the localAddress. - protocol?: string; - } - - export interface Tunnel extends TunnelDescription { - // Implementers of Tunnel should fire onDidDispose when dispose is called. - onDidDispose: Event; - dispose(): void | Thenable; - } - - /** - * Used as part of the ResolverResult if the extension has any candidate, - * published, or forwarded ports. - */ - export interface TunnelInformation { - /** - * Tunnels that are detected by the extension. The remotePort is used for display purposes. - * The localAddress should be the complete local address (ex. localhost:1234) for connecting to the port. Tunnels provided through - * detected are read-only from the forwarded ports UI. - */ - environmentTunnels?: TunnelDescription[]; - - } - - export interface TunnelCreationOptions { - /** - * True when the local operating system will require elevation to use the requested local port. - */ - elevationRequired?: boolean; - } - - export enum CandidatePortSource { - None = 0, - Process = 1, - Output = 2 - } - - export type ResolverResult = ResolvedAuthority & ResolvedOptions & TunnelInformation; - - export class RemoteAuthorityResolverError extends Error { - static NotAvailable(message?: string, handled?: boolean): RemoteAuthorityResolverError; - static TemporarilyNotAvailable(message?: string): RemoteAuthorityResolverError; - - constructor(message?: string); - } - - export interface RemoteAuthorityResolver { - /** - * Resolve the authority part of the current opened `vscode-remote://` URI. - * - * This method will be invoked once during the startup of the editor and again each time - * the editor detects a disconnection. - * - * @param authority The authority part of the current opened `vscode-remote://` URI. - * @param context A context indicating if this is the first call or a subsequent call. - */ - resolve(authority: string, context: RemoteAuthorityResolverContext): ResolverResult | Thenable; - - /** - * Get the canonical URI (if applicable) for a `vscode-remote://` URI. - * - * @returns The canonical URI or undefined if the uri is already canonical. - */ - getCanonicalURI?(uri: Uri): ProviderResult; - - /** - * Can be optionally implemented if the extension can forward ports better than the core. - * When not implemented, the core will use its default forwarding logic. - * When implemented, the core will use this to forward ports. - * - * To enable the "Change Local Port" action on forwarded ports, make sure to set the `localAddress` of - * the returned `Tunnel` to a `{ port: number, host: string; }` and not a string. - */ - tunnelFactory?: (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions) => Thenable | undefined; - - /**p - * Provides filtering for candidate ports. - */ - showCandidatePort?: (host: string, port: number, detail: string) => Thenable; - - /** - * Lets the resolver declare which tunnel factory features it supports. - * UNDER DISCUSSION! MAY CHANGE SOON. - */ - tunnelFeatures?: { - elevation: boolean; - /** - * @deprecated Use privacy instead - */ - public: boolean; - /** - * One of the the options must have the ID "private". - */ - privacyOptions: TunnelPrivacy[]; - }; - - candidatePortSource?: CandidatePortSource; - } - - /** - * More options to be used when getting an {@link AuthenticationSession} from an {@link AuthenticationProvider}. - */ - export interface AuthenticationGetSessionOptions { - /** - * Whether we should attempt to reauthenticate even if there is already a session available. - * - * If true, a modal dialog will be shown asking the user to sign in again. This is mostly used for scenarios - * where the token needs to be re minted because it has lost some authorization. - * - * Defaults to false. - */ - forceNewSession?: boolean | { detail: string }; - } - - export namespace authentication { - /** - * Get an authentication session matching the desired scopes. Rejects if a provider with providerId is not - * registered, or if the user does not consent to sharing authentication information with - * the extension. If there are multiple sessions with the same scopes, the user will be shown a - * quickpick to select which account they would like to use. - * - * Currently, there are only two authentication providers that are contributed from built in extensions - * to the editor that implement GitHub and Microsoft authentication: their providerId's are 'github' and 'microsoft'. - * @param providerId The id of the provider to use - * @param scopes A list of scopes representing the permissions requested. These are dependent on the authentication provider - * @param options The {@link AuthenticationGetSessionOptions} to use - * @returns A thenable that resolves to an authentication session - */ - export function getSession(providerId: string, scopes: readonly string[], options: AuthenticationGetSessionOptions & { forceNewSession: true | { detail: string } }): Thenable; - export function hasSession(providerId: string, scopes: readonly string[]): Thenable; - } - - export namespace workspace { - /** - * Forwards a port. If the current resolver implements RemoteAuthorityResolver:forwardPort then that will be used to make the tunnel. - * By default, openTunnel only support localhost; however, RemoteAuthorityResolver:tunnelFactory can be used to support other ips. - * - * @throws When run in an environment without a remote. - * - * @param tunnelOptions The `localPort` is a suggestion only. If that port is not available another will be chosen. - */ - export function openTunnel(tunnelOptions: TunnelOptions): Thenable; - - /** - * Gets an array of the currently available tunnels. This does not include environment tunnels, only tunnels that have been created by the user. - * Note that these are of type TunnelDescription and cannot be disposed. - */ - export let tunnels: Thenable; - - /** - * Fired when the list of tunnels has changed. - */ - export const onDidChangeTunnels: Event; - } - - export interface ResourceLabelFormatter { - scheme: string; - authority?: string; - formatting: ResourceLabelFormatting; - } - - export interface ResourceLabelFormatting { - label: string; // myLabel:/${path} - // For historic reasons we use an or string here. Once we finalize this API we should start using enums instead and adopt it in extensions. - // eslint-disable-next-line vscode-dts-literal-or-types - separator: '/' | '\\' | ''; - tildify?: boolean; - normalizeDriveLetter?: boolean; - workspaceSuffix?: string; - workspaceTooltip?: string; - authorityPrefix?: string; - stripPathStartingSeparator?: boolean; - } - - export namespace workspace { - export function registerRemoteAuthorityResolver(authorityPrefix: string, resolver: RemoteAuthorityResolver): Disposable; - export function registerResourceLabelFormatter(formatter: ResourceLabelFormatter): Disposable; - } - - export namespace env { - - /** - * The authority part of the current opened `vscode-remote://` URI. - * Defined by extensions, e.g. `ssh-remote+${host}` for remotes using a secure shell. - * - * *Note* that the value is `undefined` when there is no remote extension host but that the - * value is defined in all extension hosts (local and remote) in case a remote extension host - * exists. Use {@link Extension.extensionKind} to know if - * a specific extension runs remote or not. - */ - export const remoteAuthority: string | undefined; - - } - - //#endregion - - //#region textSearchProvider: https://github.com/microsoft/vscode/issues/59921 - - /** - * The parameters of a query for text search. - */ - export interface TextSearchQuery { - /** - * The text pattern to search for. - */ - pattern: string; - - /** - * Whether or not `pattern` should match multiple lines of text. - */ - isMultiline?: boolean; - - /** - * Whether or not `pattern` should be interpreted as a regular expression. - */ - isRegExp?: boolean; - - /** - * Whether or not the search should be case-sensitive. - */ - isCaseSensitive?: boolean; - - /** - * Whether or not to search for whole word matches only. - */ - isWordMatch?: boolean; - } - - /** - * A file glob pattern to match file paths against. - * TODO@roblourens merge this with the GlobPattern docs/definition in vscode.d.ts. - * @see {@link GlobPattern} - */ - export type GlobString = string; - - /** - * Options common to file and text search - */ - export interface SearchOptions { - /** - * The root folder to search within. - */ - folder: Uri; - - /** - * Files that match an `includes` glob pattern should be included in the search. - */ - includes: GlobString[]; - - /** - * Files that match an `excludes` glob pattern should be excluded from the search. - */ - excludes: GlobString[]; - - /** - * Whether external files that exclude files, like .gitignore, should be respected. - * See the vscode setting `"search.useIgnoreFiles"`. - */ - useIgnoreFiles: boolean; - - /** - * Whether symlinks should be followed while searching. - * See the vscode setting `"search.followSymlinks"`. - */ - followSymlinks: boolean; - - /** - * Whether global files that exclude files, like .gitignore, should be respected. - * See the vscode setting `"search.useGlobalIgnoreFiles"`. - */ - useGlobalIgnoreFiles: boolean; - } - - /** - * Options to specify the size of the result text preview. - * These options don't affect the size of the match itself, just the amount of preview text. - */ - export interface TextSearchPreviewOptions { - /** - * The maximum number of lines in the preview. - * Only search providers that support multiline search will ever return more than one line in the match. - */ - matchLines: number; - - /** - * The maximum number of characters included per line. - */ - charsPerLine: number; - } - - /** - * Options that apply to text search. - */ - export interface TextSearchOptions extends SearchOptions { - /** - * The maximum number of results to be returned. - */ - maxResults: number; - - /** - * Options to specify the size of the result text preview. - */ - previewOptions?: TextSearchPreviewOptions; - - /** - * Exclude files larger than `maxFileSize` in bytes. - */ - maxFileSize?: number; - - /** - * Interpret files using this encoding. - * See the vscode setting `"files.encoding"` - */ - encoding?: string; - - /** - * Number of lines of context to include before each match. - */ - beforeContext?: number; - - /** - * Number of lines of context to include after each match. - */ - afterContext?: number; - } - - /** - * Represents the severiry of a TextSearchComplete message. - */ - export enum TextSearchCompleteMessageType { - Information = 1, - Warning = 2, - } - - /** - * A message regarding a completed search. - */ - export interface TextSearchCompleteMessage { - /** - * Markdown text of the message. - */ - text: string, - /** - * Whether the source of the message is trusted, command links are disabled for untrusted message sources. - * Messaged are untrusted by default. - */ - trusted?: boolean, - /** - * The message type, this affects how the message will be rendered. - */ - type: TextSearchCompleteMessageType, - } - - /** - * Information collected when text search is complete. - */ - export interface TextSearchComplete { - /** - * Whether the search hit the limit on the maximum number of search results. - * `maxResults` on {@linkcode TextSearchOptions} specifies the max number of results. - * - If exactly that number of matches exist, this should be false. - * - If `maxResults` matches are returned and more exist, this should be true. - * - If search hits an internal limit which is less than `maxResults`, this should be true. - */ - limitHit?: boolean; - - /** - * Additional information regarding the state of the completed search. - * - * Messages with "Information" style support links in markdown syntax: - * - Click to [run a command](command:workbench.action.OpenQuickPick) - * - Click to [open a website](https://aka.ms) - * - * Commands may optionally return { triggerSearch: true } to signal to the editor that the original search should run be again. - */ - message?: TextSearchCompleteMessage | TextSearchCompleteMessage[]; - } - - /** - * A preview of the text result. - */ - export interface TextSearchMatchPreview { - /** - * The matching lines of text, or a portion of the matching line that contains the match. - */ - text: string; - - /** - * The Range within `text` corresponding to the text of the match. - * The number of matches must match the TextSearchMatch's range property. - */ - matches: Range | Range[]; - } - - /** - * A match from a text search - */ - export interface TextSearchMatch { - /** - * The uri for the matching document. - */ - uri: Uri; - - /** - * The range of the match within the document, or multiple ranges for multiple matches. - */ - ranges: Range | Range[]; - - /** - * A preview of the text match. - */ - preview: TextSearchMatchPreview; - } - - /** - * A line of context surrounding a TextSearchMatch. - */ - export interface TextSearchContext { - /** - * The uri for the matching document. - */ - uri: Uri; - - /** - * One line of text. - * previewOptions.charsPerLine applies to this - */ - text: string; - - /** - * The line number of this line of context. - */ - lineNumber: number; - } - - export type TextSearchResult = TextSearchMatch | TextSearchContext; - - /** - * A TextSearchProvider provides search results for text results inside files in the workspace. - */ - export interface TextSearchProvider { - /** - * Provide results that match the given text pattern. - * @param query The parameters for this query. - * @param options A set of options to consider while searching. - * @param progress A progress callback that must be invoked for all results. - * @param token A cancellation token. - */ - provideTextSearchResults(query: TextSearchQuery, options: TextSearchOptions, progress: Progress, token: CancellationToken): ProviderResult; - } - - //#endregion - - //#region fileSearchProvider: https://github.com/microsoft/vscode/issues/73524 - - /** - * The parameters of a query for file search. - */ - export interface FileSearchQuery { - /** - * The search pattern to match against file paths. - */ - pattern: string; - } - - /** - * Options that apply to file search. - */ - export interface FileSearchOptions extends SearchOptions { - /** - * The maximum number of results to be returned. - */ - maxResults?: number; - - /** - * A CancellationToken that represents the session for this search query. If the provider chooses to, this object can be used as the key for a cache, - * and searches with the same session object can search the same cache. When the token is cancelled, the session is complete and the cache can be cleared. - */ - session?: CancellationToken; - } - - /** - * A FileSearchProvider provides search results for files in the given folder that match a query string. It can be invoked by quickopen or other extensions. - * - * A FileSearchProvider is the more powerful of two ways to implement file search in the editor. Use a FileSearchProvider if you wish to search within a folder for - * all files that match the user's query. - * - * The FileSearchProvider will be invoked on every keypress in quickopen. When `workspace.findFiles` is called, it will be invoked with an empty query string, - * and in that case, every file in the folder should be returned. - */ - export interface FileSearchProvider { - /** - * Provide the set of files that match a certain file path pattern. - * @param query The parameters for this query. - * @param options A set of options to consider while searching files. - * @param token A cancellation token. - */ - provideFileSearchResults(query: FileSearchQuery, options: FileSearchOptions, token: CancellationToken): ProviderResult; - } - - export namespace workspace { - /** - * Register a search provider. - * - * Only one provider can be registered per scheme. - * - * @param scheme The provider will be invoked for workspace folders that have this file scheme. - * @param provider The provider. - * @return A {@link Disposable} that unregisters this provider when being disposed. - */ - export function registerFileSearchProvider(scheme: string, provider: FileSearchProvider): Disposable; - - /** - * Register a text search provider. - * - * Only one provider can be registered per scheme. - * - * @param scheme The provider will be invoked for workspace folders that have this file scheme. - * @param provider The provider. - * @return A {@link Disposable} that unregisters this provider when being disposed. - */ - export function registerTextSearchProvider(scheme: string, provider: TextSearchProvider): Disposable; - } - - //#endregion - - //#region findTextInFiles: https://github.com/microsoft/vscode/issues/59924 - - /** - * Options that can be set on a findTextInFiles search. - */ - export interface FindTextInFilesOptions { - /** - * A {@link GlobPattern glob pattern} that defines the files to search for. The glob pattern - * will be matched against the file paths of files relative to their workspace. Use a {@link RelativePattern relative pattern} - * to restrict the search results to a {@link WorkspaceFolder workspace folder}. - */ - include?: GlobPattern; - - /** - * A {@link GlobPattern glob pattern} that defines files and folders to exclude. The glob pattern - * will be matched against the file paths of resulting matches relative to their workspace. When `undefined`, default excludes will - * apply. - */ - exclude?: GlobPattern; - - /** - * Whether to use the default and user-configured excludes. Defaults to true. - */ - useDefaultExcludes?: boolean; - - /** - * The maximum number of results to search for - */ - maxResults?: number; - - /** - * Whether external files that exclude files, like .gitignore, should be respected. - * See the vscode setting `"search.useIgnoreFiles"`. - */ - useIgnoreFiles?: boolean; - - /** - * Whether global files that exclude files, like .gitignore, should be respected. - * See the vscode setting `"search.useGlobalIgnoreFiles"`. - */ - useGlobalIgnoreFiles?: boolean; - - /** - * Whether symlinks should be followed while searching. - * See the vscode setting `"search.followSymlinks"`. - */ - followSymlinks?: boolean; - - /** - * Interpret files using this encoding. - * See the vscode setting `"files.encoding"` - */ - encoding?: string; - - /** - * Options to specify the size of the result text preview. - */ - previewOptions?: TextSearchPreviewOptions; - - /** - * Number of lines of context to include before each match. - */ - beforeContext?: number; - - /** - * Number of lines of context to include after each match. - */ - afterContext?: number; - } - - export namespace workspace { - /** - * Search text in files across all {@link workspace.workspaceFolders workspace folders} in the workspace. - * @param query The query parameters for the search - the search string, whether it's case-sensitive, or a regex, or matches whole words. - * @param callback A callback, called for each result - * @param token A token that can be used to signal cancellation to the underlying search engine. - * @return A thenable that resolves when the search is complete. - */ - export function findTextInFiles(query: TextSearchQuery, callback: (result: TextSearchResult) => void, token?: CancellationToken): Thenable; - - /** - * Search text in files across all {@link workspace.workspaceFolders workspace folders} in the workspace. - * @param query The query parameters for the search - the search string, whether it's case-sensitive, or a regex, or matches whole words. - * @param options An optional set of query options. Include and exclude patterns, maxResults, etc. - * @param callback A callback, called for each result - * @param token A token that can be used to signal cancellation to the underlying search engine. - * @return A thenable that resolves when the search is complete. - */ - export function findTextInFiles(query: TextSearchQuery, options: FindTextInFilesOptions, callback: (result: TextSearchResult) => void, token?: CancellationToken): Thenable; - } - - //#endregion - - //#region diffCommand: https://github.com/microsoft/vscode/issues/84899 - - /** - * The contiguous set of modified lines in a diff. - */ - export interface LineChange { - readonly originalStartLineNumber: number; - readonly originalEndLineNumber: number; - readonly modifiedStartLineNumber: number; - readonly modifiedEndLineNumber: number; - } - - export namespace commands { - - /** - * Registers a diff information command that can be invoked via a keyboard shortcut, - * a menu item, an action, or directly. - * - * Diff information commands are different from ordinary {@link commands.registerCommand commands} as - * they only execute when there is an active diff editor when the command is called, and the diff - * information has been computed. Also, the command handler of an editor command has access to - * the diff information. - * - * @param command A unique identifier for the command. - * @param callback A command handler function with access to the {@link LineChange diff information}. - * @param thisArg The `this` context used when invoking the handler function. - * @return Disposable which unregisters this command on disposal. - */ - export function registerDiffInformationCommand(command: string, callback: (diff: LineChange[], ...args: any[]) => any, thisArg?: any): Disposable; - } - - //#endregion - - // eslint-disable-next-line vscode-dts-region-comments - //#region notebookDebugOptions: @roblourens: new debug session option for simple UI 'managedByParent' (see https://github.com/microsoft/vscode/issues/128588) - - /** - * Options for {@link debug.startDebugging starting a debug session}. - */ - export interface DebugSessionOptions { - - debugUI?: { - /** - * When true, the debug toolbar will not be shown for this session, the window statusbar color will not be changed, and the debug viewlet will not be automatically revealed. - */ - simple?: boolean; - } - - /** - * When true, a save will not be triggered for open editors when starting a debug session, regardless of the value of the `debug.saveBeforeStart` setting. - */ - suppressSaveBeforeStart?: boolean; - } - - //#endregion - - // #region scmValidation: @joaomoreno - - /** - * Represents the validation type of the Source Control input. - */ - export enum SourceControlInputBoxValidationType { - - /** - * Something not allowed by the rules of a language or other means. - */ - Error = 0, - - /** - * Something suspicious but allowed. - */ - Warning = 1, - - /** - * Something to inform about but not a problem. - */ - Information = 2 - } - - export interface SourceControlInputBoxValidation { - - /** - * The validation message to display. - */ - readonly message: string | MarkdownString; - - /** - * The validation type. - */ - readonly type: SourceControlInputBoxValidationType; - } - - /** - * Represents the input box in the Source Control viewlet. - */ - export interface SourceControlInputBox { - - /** - * Shows a transient contextual message on the input. - */ - showValidationMessage(message: string | MarkdownString, type: SourceControlInputBoxValidationType): void; - - /** - * A validation function for the input box. It's possible to change - * the validation provider simply by setting this property to a different function. - */ - validateInput?(value: string, cursorPosition: number): ProviderResult; - } - - //#endregion - - //#region scmSelectedProvider: @joaomoreno - - export interface SourceControl { - - /** - * Whether the source control is selected. - */ - readonly selected: boolean; - - /** - * An event signaling when the selection state changes. - */ - readonly onDidChangeSelection: Event; - } - - //#endregion - - //#region terminalDataWriteEvent: https://github.com/microsoft/vscode/issues/78502 - - export interface TerminalDataWriteEvent { - /** - * The {@link Terminal} for which the data was written. - */ - readonly terminal: Terminal; - /** - * The data being written. - */ - readonly data: string; - } - - namespace window { - /** - * An event which fires when the terminal's child pseudo-device is written to (the shell). - * In other words, this provides access to the raw data stream from the process running - * within the terminal, including VT sequences. - */ - export const onDidWriteTerminalData: Event; - } - - //#endregion - - //#region terminalDimensions: https://github.com/microsoft/vscode/issues/55718 - - /** - * An {@link Event} which fires when a {@link Terminal}'s dimensions change. - */ - export interface TerminalDimensionsChangeEvent { - /** - * The {@link Terminal} for which the dimensions have changed. - */ - readonly terminal: Terminal; - /** - * The new value for the {@link Terminal.dimensions terminal's dimensions}. - */ - readonly dimensions: TerminalDimensions; - } - - export namespace window { - /** - * An event which fires when the {@link Terminal.dimensions dimensions} of the terminal change. - */ - export const onDidChangeTerminalDimensions: Event; - } - - export interface Terminal { - /** - * The current dimensions of the terminal. This will be `undefined` immediately after the - * terminal is created as the dimensions are not known until shortly after the terminal is - * created. - */ - readonly dimensions: TerminalDimensions | undefined; - } - - //#endregion - - //#region terminalLocation: https://github.com/microsoft/vscode/issues/45407 - - export interface TerminalOptions { - location?: TerminalLocation | TerminalEditorLocationOptions | TerminalSplitLocationOptions; - } - - export interface ExtensionTerminalOptions { - location?: TerminalLocation | TerminalEditorLocationOptions | TerminalSplitLocationOptions; - } - - export enum TerminalLocation { - Panel = 1, - Editor = 2, - } - - export interface TerminalEditorLocationOptions { - /** - * A view column in which the {@link Terminal terminal} should be shown in the editor area. - * Use {@link ViewColumn.Active active} to open in the active editor group, other values are - * adjusted to be `Min(column, columnCount + 1)`, the - * {@link ViewColumn.Active active}-column is not adjusted. Use - * {@linkcode ViewColumn.Beside} to open the editor to the side of the currently active one. - */ - viewColumn: ViewColumn; - /** - * An optional flag that when `true` will stop the {@link Terminal} from taking focus. - */ - preserveFocus?: boolean; - } - - export interface TerminalSplitLocationOptions { - /** - * The parent terminal to split this terminal beside. This works whether the parent terminal - * is in the panel or the editor area. - */ - parentTerminal: Terminal; - } - - //#endregion - - //#region terminalNameChangeEvent: https://github.com/microsoft/vscode/issues/114898 - - export interface Pseudoterminal { - /** - * An event that when fired allows changing the name of the terminal. - * - * **Example:** Change the terminal name to "My new terminal". - * ```typescript - * const writeEmitter = new vscode.EventEmitter(); - * const changeNameEmitter = new vscode.EventEmitter(); - * const pty: vscode.Pseudoterminal = { - * onDidWrite: writeEmitter.event, - * onDidChangeName: changeNameEmitter.event, - * open: () => changeNameEmitter.fire('My new terminal'), - * close: () => {} - * }; - * vscode.window.createTerminal({ name: 'My terminal', pty }); - * ``` - */ - onDidChangeName?: Event; - } - - //#endregion - - //#region exclusiveDocumentFilters: @jrieken - - export interface DocumentFilter { - readonly exclusive?: boolean; - } - - //#endregion - - //#region treeViewReveal: https://github.com/microsoft/vscode/issues/61313 @alexr00 - export interface TreeView extends Disposable { - reveal(element: T | undefined, options?: { select?: boolean, focus?: boolean, expand?: boolean | number; }): Thenable; - } - //#endregion - - //#region treeViewDragAndDrop: https://github.com/microsoft/vscode/issues/32592 - /** - * A data provider that provides tree data - */ - export interface TreeDataProvider { - /** - * An optional event to signal that an element or root has changed. - * This will trigger the view to update the changed element/root and its children recursively (if shown). - * To signal that root has changed, do not pass any argument or pass `undefined` or `null`. - */ - onDidChangeTreeData2?: Event; - } - - export interface TreeViewOptions { - /** - * An optional interface to implement drag and drop in the tree view. - */ - dragAndDropController?: DragAndDropController; - } - - export interface TreeDataTransferItem { - asString(): Thenable; - } - - export interface TreeDataTransfer { - /** - * A map containing a mapping of the mime type of the corresponding data. - * The type for tree elements is text/treeitem. - * For example, you can reconstruct the your tree elements: - * ```ts - * JSON.parse(await (items.get('text/treeitem')!.asString())) - * ``` - */ - items: { get: (mimeType: string) => TreeDataTransferItem | undefined }; - } - - export interface DragAndDropController extends Disposable { - readonly supportedTypes: string[]; - - /** - * todo@API maybe - * - * When the user drops an item from this DragAndDropController on **another tree item** in **the same tree**, - * `onWillDrop` will be called with the dropped tree item. This is the DragAndDropController's opportunity to - * package the data from the dropped tree item into whatever format they want the target tree item to receive. - * - * The returned `TreeDataTransfer` will be merged with the original`TreeDataTransfer` for the operation. - * - * Note for implementation later: This means that the `text/treeItem` mime type will go away. - * - * @param source - */ - // onWillDrop?(source: T): Thenable; - - /** - * Extensions should fire `TreeDataProvider.onDidChangeTreeData` for any elements that need to be refreshed. - * - * @param source - * @param target - */ - onDrop(source: TreeDataTransfer, target: T): Thenable; - } - //#endregion - - //#region taskPresentationGroup: https://github.com/microsoft/vscode/issues/47265 - export interface TaskPresentationOptions { - /** - * Controls whether the task is executed in a specific terminal group using split panes. - */ - group?: string; - - /** - * Controls whether the terminal is closed after executing the task. - */ - close?: boolean; - } - //#endregion - - //#region customEditorMove: https://github.com/microsoft/vscode/issues/86146 - - // TODO: Also for custom editor - - export interface CustomTextEditorProvider { - - /** - * Handle when the underlying resource for a custom editor is renamed. - * - * This allows the webview for the editor be preserved throughout the rename. If this method is not implemented, - * the editor will destroy the previous custom editor and create a replacement one. - * - * @param newDocument New text document to use for the custom editor. - * @param existingWebviewPanel Webview panel for the custom editor. - * @param token A cancellation token that indicates the result is no longer needed. - * - * @return Thenable indicating that the webview editor has been moved. - */ - // eslint-disable-next-line vscode-dts-provider-naming - moveCustomTextEditor?(newDocument: TextDocument, existingWebviewPanel: WebviewPanel, token: CancellationToken): Thenable; - } - - //#endregion - - //#region quickPickSortByLabel: https://github.com/microsoft/vscode/issues/73904 - - export interface QuickPick extends QuickInput { - /** - * An optional flag to sort the final results by index of first query match in label. Defaults to true. - */ - sortByLabel: boolean; - } - - //#endregion - - //#region notebookCellExecutionState: https://github.com/microsoft/vscode/issues/124970 - - /** - * The execution state of a notebook cell. - */ - export enum NotebookCellExecutionState { - /** - * The cell is idle. - */ - Idle = 1, - /** - * Execution for the cell is pending. - */ - Pending = 2, - /** - * The cell is currently executing. - */ - Executing = 3, - } - - /** - * An event describing a cell execution state change. - */ - export interface NotebookCellExecutionStateChangeEvent { - /** - * The {@link NotebookCell cell} for which the execution state has changed. - */ - readonly cell: NotebookCell; - - /** - * The new execution state of the cell. - */ - readonly state: NotebookCellExecutionState; - } - - export namespace notebooks { - - /** - * An {@link Event} which fires when the execution state of a cell has changed. - */ - // todo@API this is an event that is fired for a property that cells don't have and that makes me wonder - // how a correct consumer works, e.g the consumer could have been late and missed an event? - export const onDidChangeNotebookCellExecutionState: Event; - } - - //#endregion - - //#region notebookDeprecated: https://github.com/microsoft/vscode/issues/106744 - - export interface NotebookCellOutput { - id: string; - } - - //#endregion - - //#region notebookEditor: https://github.com/microsoft/vscode/issues/106744 - - /** - * Represents a notebook editor that is attached to a {@link NotebookDocument notebook}. - */ - export enum NotebookEditorRevealType { - /** - * The range will be revealed with as little scrolling as possible. - */ - Default = 0, - - /** - * The range will always be revealed in the center of the viewport. - */ - InCenter = 1, - - /** - * If the range is outside the viewport, it will be revealed in the center of the viewport. - * Otherwise, it will be revealed with as little scrolling as possible. - */ - InCenterIfOutsideViewport = 2, - - /** - * The range will always be revealed at the top of the viewport. - */ - AtTop = 3 - } - - /** - * Represents a notebook editor that is attached to a {@link NotebookDocument notebook}. - */ - export interface NotebookEditor { - /** - * The document associated with this notebook editor. - */ - //todo@api rename to notebook? - readonly document: NotebookDocument; - - /** - * The selections on this notebook editor. - * - * The primary selection (or focused range) is `selections[0]`. When the document has no cells, the primary selection is empty `{ start: 0, end: 0 }`; - */ - selections: NotebookRange[]; - - /** - * The current visible ranges in the editor (vertically). - */ - readonly visibleRanges: NotebookRange[]; - - /** - * Scroll as indicated by `revealType` in order to reveal the given range. - * - * @param range A range. - * @param revealType The scrolling strategy for revealing `range`. - */ - revealRange(range: NotebookRange, revealType?: NotebookEditorRevealType): void; - - /** - * The column in which this editor shows. - */ - readonly viewColumn?: ViewColumn; - } - - export interface NotebookDocumentMetadataChangeEvent { - /** - * The {@link NotebookDocument notebook document} for which the document metadata have changed. - */ - //todo@API rename to notebook? - readonly document: NotebookDocument; - } - - export interface NotebookCellsChangeData { - readonly start: number; - // todo@API end? Use NotebookCellRange instead? - readonly deletedCount: number; - // todo@API removedCells, deletedCells? - readonly deletedItems: NotebookCell[]; - // todo@API addedCells, insertedCells, newCells? - readonly items: NotebookCell[]; - } - - export interface NotebookCellsChangeEvent { - /** - * The {@link NotebookDocument notebook document} for which the cells have changed. - */ - //todo@API rename to notebook? - readonly document: NotebookDocument; - readonly changes: ReadonlyArray; - } - - export interface NotebookCellOutputsChangeEvent { - /** - * The {@link NotebookDocument notebook document} for which the cell outputs have changed. - */ - //todo@API remove? use cell.notebook instead? - readonly document: NotebookDocument; - // NotebookCellOutputsChangeEvent.cells vs NotebookCellMetadataChangeEvent.cell - readonly cells: NotebookCell[]; - } - - export interface NotebookCellMetadataChangeEvent { - /** - * The {@link NotebookDocument notebook document} for which the cell metadata have changed. - */ - //todo@API remove? use cell.notebook instead? - readonly document: NotebookDocument; - // NotebookCellOutputsChangeEvent.cells vs NotebookCellMetadataChangeEvent.cell - readonly cell: NotebookCell; - } - - export interface NotebookEditorSelectionChangeEvent { - /** - * The {@link NotebookEditor notebook editor} for which the selections have changed. - */ - readonly notebookEditor: NotebookEditor; - readonly selections: ReadonlyArray - } - - export interface NotebookEditorVisibleRangesChangeEvent { - /** - * The {@link NotebookEditor notebook editor} for which the visible ranges have changed. - */ - readonly notebookEditor: NotebookEditor; - readonly visibleRanges: ReadonlyArray; - } - - - export interface NotebookDocumentShowOptions { - viewColumn?: ViewColumn; - preserveFocus?: boolean; - preview?: boolean; - selections?: NotebookRange[]; - } - - export namespace notebooks { - - - - export const onDidSaveNotebookDocument: Event; - - export const onDidChangeNotebookDocumentMetadata: Event; - export const onDidChangeNotebookCells: Event; - - // todo@API add onDidChangeNotebookCellOutputs - export const onDidChangeCellOutputs: Event; - - // todo@API add onDidChangeNotebookCellMetadata - export const onDidChangeCellMetadata: Event; - } - - export namespace window { - export const visibleNotebookEditors: NotebookEditor[]; - export const onDidChangeVisibleNotebookEditors: Event; - export const activeNotebookEditor: NotebookEditor | undefined; - export const onDidChangeActiveNotebookEditor: Event; - export const onDidChangeNotebookEditorSelection: Event; - export const onDidChangeNotebookEditorVisibleRanges: Event; - - export function showNotebookDocument(uri: Uri, options?: NotebookDocumentShowOptions): Thenable; - export function showNotebookDocument(document: NotebookDocument, options?: NotebookDocumentShowOptions): Thenable; - } - - //#endregion - - //#region notebookEditorEdit: https://github.com/microsoft/vscode/issues/106744 - - // todo@API add NotebookEdit-type which handles all these cases? - // export class NotebookEdit { - // range: NotebookRange; - // newCells: NotebookCellData[]; - // newMetadata?: NotebookDocumentMetadata; - // constructor(range: NotebookRange, newCells: NotebookCellData) - // } - - // export class NotebookCellEdit { - // newMetadata?: NotebookCellMetadata; - // } - - // export interface WorkspaceEdit { - // set(uri: Uri, edits: TextEdit[] | NotebookEdit[]): void - // } - - export interface WorkspaceEdit { - // todo@API add NotebookEdit-type which handles all these cases? - replaceNotebookMetadata(uri: Uri, value: { [key: string]: any }): void; - replaceNotebookCells(uri: Uri, range: NotebookRange, cells: NotebookCellData[], metadata?: WorkspaceEditEntryMetadata): void; - replaceNotebookCellMetadata(uri: Uri, index: number, cellMetadata: { [key: string]: any }, metadata?: WorkspaceEditEntryMetadata): void; - } - - export interface NotebookEditorEdit { - replaceMetadata(value: { [key: string]: any }): void; - replaceCells(start: number, end: number, cells: NotebookCellData[]): void; - replaceCellMetadata(index: number, metadata: { [key: string]: any }): void; - } - - export interface NotebookEditor { - /** - * Perform an edit on the notebook associated with this notebook editor. - * - * The given callback-function is invoked with an {@link NotebookEditorEdit edit-builder} which must - * be used to make edits. Note that the edit-builder is only valid while the - * callback executes. - * - * @param callback A function which can create edits using an {@link NotebookEditorEdit edit-builder}. - * @return A promise that resolves with a value indicating if the edits could be applied. - */ - // @jrieken REMOVE maybe - edit(callback: (editBuilder: NotebookEditorEdit) => void): Thenable; - } - - //#endregion - - //#region notebookEditorDecorationType: https://github.com/microsoft/vscode/issues/106744 - - export interface NotebookEditor { - setDecorations(decorationType: NotebookEditorDecorationType, range: NotebookRange): void; - } - - export interface NotebookDecorationRenderOptions { - backgroundColor?: string | ThemeColor; - borderColor?: string | ThemeColor; - top?: ThemableDecorationAttachmentRenderOptions; - } - - export interface NotebookEditorDecorationType { - readonly key: string; - dispose(): void; - } - - export namespace notebooks { - export function createNotebookEditorDecorationType(options: NotebookDecorationRenderOptions): NotebookEditorDecorationType; - } - - //#endregion - - //#region notebookConcatTextDocument: https://github.com/microsoft/vscode/issues/106744 - - export namespace notebooks { - /** - * Create a document that is the concatenation of all notebook cells. By default all code-cells are included - * but a selector can be provided to narrow to down the set of cells. - * - * @param notebook - * @param selector - */ - // todo@API really needed? we didn't find a user here - export function createConcatTextDocument(notebook: NotebookDocument, selector?: DocumentSelector): NotebookConcatTextDocument; - } - - export interface NotebookConcatTextDocument { - readonly uri: Uri; - readonly isClosed: boolean; - dispose(): void; - readonly onDidChange: Event; - readonly version: number; - getText(): string; - getText(range: Range): string; - - offsetAt(position: Position): number; - positionAt(offset: number): Position; - validateRange(range: Range): Range; - validatePosition(position: Position): Position; - - locationAt(positionOrRange: Position | Range): Location; - positionAt(location: Location): Position; - contains(uri: Uri): boolean; - } - - //#endregion - - //#region notebookContentProvider: https://github.com/microsoft/vscode/issues/106744 - - interface NotebookDocumentBackup { - /** - * Unique identifier for the backup. - * - * This id is passed back to your extension in `openNotebook` when opening a notebook editor from a backup. - */ - readonly id: string; - - /** - * Delete the current backup. - * - * This is called by the editor when it is clear the current backup is no longer needed, such as when a new backup - * is made or when the file is saved. - */ - delete(): void; - } - - interface NotebookDocumentBackupContext { - readonly destination: Uri; - } - - interface NotebookDocumentOpenContext { - readonly backupId?: string; - readonly untitledDocumentData?: Uint8Array; - } - - // todo@API use openNotebookDOCUMENT to align with openCustomDocument etc? - // todo@API rename to NotebookDocumentContentProvider - export interface NotebookContentProvider { - - readonly options?: NotebookDocumentContentOptions; - readonly onDidChangeNotebookContentOptions?: Event; - - /** - * Content providers should always use {@link FileSystemProvider file system providers} to - * resolve the raw content for `uri` as the resouce is not necessarily a file on disk. - */ - openNotebook(uri: Uri, openContext: NotebookDocumentOpenContext, token: CancellationToken): NotebookData | Thenable; - - // todo@API use NotebookData instead - saveNotebook(document: NotebookDocument, token: CancellationToken): Thenable; - - // todo@API use NotebookData instead - saveNotebookAs(targetResource: Uri, document: NotebookDocument, token: CancellationToken): Thenable; - - // todo@API use NotebookData instead - backupNotebook(document: NotebookDocument, context: NotebookDocumentBackupContext, token: CancellationToken): Thenable; - } - - export namespace workspace { - - // TODO@api use NotebookDocumentFilter instead of just notebookType:string? - // TODO@API options duplicates the more powerful variant on NotebookContentProvider - export function registerNotebookContentProvider(notebookType: string, provider: NotebookContentProvider, options?: NotebookDocumentContentOptions): Disposable; - } - - //#endregion - - //#region notebookLiveShare: https://github.com/microsoft/vscode/issues/106744 - - export interface NotebookRegistrationData { - displayName: string; - filenamePattern: (GlobPattern | { include: GlobPattern; exclude: GlobPattern; })[]; - exclusive?: boolean; - } - - export namespace workspace { - // SPECIAL overload with NotebookRegistrationData - export function registerNotebookContentProvider(notebookType: string, provider: NotebookContentProvider, options?: NotebookDocumentContentOptions, registrationData?: NotebookRegistrationData): Disposable; - // SPECIAL overload with NotebookRegistrationData - export function registerNotebookSerializer(notebookType: string, serializer: NotebookSerializer, options?: NotebookDocumentContentOptions, registration?: NotebookRegistrationData): Disposable; - } - - //#endregion - - //#region notebookMessaging: https://github.com/microsoft/vscode/issues/123601 - - /** - * Represents a script that is loaded into the notebook renderer before rendering output. This allows - * to provide and share functionality for notebook markup and notebook output renderers. - */ - export class NotebookRendererScript { - - /** - * APIs that the preload provides to the renderer. These are matched - * against the `dependencies` and `optionalDependencies` arrays in the - * notebook renderer contribution point. - */ - provides: string[]; - - /** - * URI of the JavaScript module to preload. - * - * This module must export an `activate` function that takes a context object that contains the notebook API. - */ - uri: Uri; - - /** - * @param uri URI of the JavaScript module to preload - * @param provides Value for the `provides` property - */ - constructor(uri: Uri, provides?: string | string[]); - } - - export interface NotebookController { - /** - * The human-readable label used to categorise controllers. - */ - kind?: string; - - // todo@API allow add, not remove - readonly rendererScripts: NotebookRendererScript[]; - - /** - * An event that fires when a {@link NotebookController.rendererScripts renderer script} has send a message to - * the controller. - */ - readonly onDidReceiveMessage: Event<{ editor: NotebookEditor, message: any }>; - - /** - * Send a message to the renderer of notebook editors. - * - * Note that only editors showing documents that are bound to this controller - * are receiving the message. - * - * @param message The message to send. - * @param editor A specific editor to send the message to. When `undefined` all applicable editors are receiving the message. - * @returns A promise that resolves to a boolean indicating if the message has been send or not. - */ - postMessage(message: any, editor?: NotebookEditor): Thenable; - - //todo@API validate this works - asWebviewUri(localResource: Uri): Uri; - } - - export namespace notebooks { - - export function createNotebookController(id: string, viewType: string, label: string, handler?: (cells: NotebookCell[], notebook: NotebookDocument, controller: NotebookController) => void | Thenable, rendererScripts?: NotebookRendererScript[]): NotebookController; - } - - //#endregion - - //#region timeline: https://github.com/microsoft/vscode/issues/84297 - - export class TimelineItem { - /** - * A timestamp (in milliseconds since 1 January 1970 00:00:00) for when the timeline item occurred. - */ - timestamp: number; - - /** - * A human-readable string describing the timeline item. - */ - label: string; - - /** - * Optional id for the timeline item. It must be unique across all the timeline items provided by this source. - * - * If not provided, an id is generated using the timeline item's timestamp. - */ - id?: string; - - /** - * The icon path or {@link ThemeIcon} for the timeline item. - */ - iconPath?: Uri | { light: Uri; dark: Uri; } | ThemeIcon; - - /** - * A human readable string describing less prominent details of the timeline item. - */ - description?: string; - - /** - * The tooltip text when you hover over the timeline item. - */ - detail?: string; - - /** - * The {@link Command} that should be executed when the timeline item is selected. - */ - command?: Command; - - /** - * Context value of the timeline item. This can be used to contribute specific actions to the item. - * For example, a timeline item is given a context value as `commit`. When contributing actions to `timeline/item/context` - * using `menus` extension point, you can specify context value for key `timelineItem` in `when` expression like `timelineItem == commit`. - * ``` - * "contributes": { - * "menus": { - * "timeline/item/context": [ - * { - * "command": "extension.copyCommitId", - * "when": "timelineItem == commit" - * } - * ] - * } - * } - * ``` - * This will show the `extension.copyCommitId` action only for items where `contextValue` is `commit`. - */ - contextValue?: string; - - /** - * Accessibility information used when screen reader interacts with this timeline item. - */ - accessibilityInformation?: AccessibilityInformation; - - /** - * @param label A human-readable string describing the timeline item - * @param timestamp A timestamp (in milliseconds since 1 January 1970 00:00:00) for when the timeline item occurred - */ - constructor(label: string, timestamp: number); - } - - export interface TimelineChangeEvent { - /** - * The {@link Uri} of the resource for which the timeline changed. - */ - uri: Uri; - - /** - * A flag which indicates whether the entire timeline should be reset. - */ - reset?: boolean; - } - - export interface Timeline { - readonly paging?: { - /** - * A provider-defined cursor specifying the starting point of timeline items which are after the ones returned. - * Use `undefined` to signal that there are no more items to be returned. - */ - readonly cursor: string | undefined; - }; - - /** - * An array of {@link TimelineItem timeline items}. - */ - readonly items: readonly TimelineItem[]; - } - - export interface TimelineOptions { - /** - * A provider-defined cursor specifying the starting point of the timeline items that should be returned. - */ - cursor?: string; - - /** - * An optional maximum number timeline items or the all timeline items newer (inclusive) than the timestamp or id that should be returned. - * If `undefined` all timeline items should be returned. - */ - limit?: number | { timestamp: number; id?: string; }; - } - - export interface TimelineProvider { - /** - * An optional event to signal that the timeline for a source has changed. - * To signal that the timeline for all resources (uris) has changed, do not pass any argument or pass `undefined`. - */ - onDidChange?: Event; - - /** - * An identifier of the source of the timeline items. This can be used to filter sources. - */ - readonly id: string; - - /** - * A human-readable string describing the source of the timeline items. This can be used as the display label when filtering sources. - */ - readonly label: string; - - /** - * Provide {@link TimelineItem timeline items} for a {@link Uri}. - * - * @param uri The {@link Uri} of the file to provide the timeline for. - * @param options A set of options to determine how results should be returned. - * @param token A cancellation token. - * @return The {@link TimelineResult timeline result} or a thenable that resolves to such. The lack of a result - * can be signaled by returning `undefined`, `null`, or an empty array. - */ - provideTimeline(uri: Uri, options: TimelineOptions, token: CancellationToken): ProviderResult; - } - - export namespace workspace { - /** - * Register a timeline provider. - * - * Multiple providers can be registered. In that case, providers are asked in - * parallel and the results are merged. A failing provider (rejected promise or exception) will - * not cause a failure of the whole operation. - * - * @param scheme A scheme or schemes that defines which documents this provider is applicable to. Can be `*` to target all documents. - * @param provider A timeline provider. - * @return A {@link Disposable} that unregisters this provider when being disposed. - */ - export function registerTimelineProvider(scheme: string | string[], provider: TimelineProvider): Disposable; - } - - //#endregion - - //#region tokenInformation: https://github.com/microsoft/vscode/issues/91555 - - export enum StandardTokenType { - Other = 0, - Comment = 1, - String = 2, - RegEx = 4 - } - - export interface TokenInformation { - type: StandardTokenType; - range: Range; - } - - export namespace languages { - export function getTokenInformationAtPosition(document: TextDocument, position: Position): Thenable; - } - - //#endregion - - //#region inlayHints: https://github.com/microsoft/vscode/issues/16221 - - // todo@API Split between Inlay- and OverlayHints (InlayHint are for a position, OverlayHints for a non-empty range) - // todo@API add "mini-markdown" for links and styles - // (done) remove description - // (done) rename to InlayHint - // (done) add InlayHintKind with type, argument, etc - - export namespace languages { - /** - * Register a inlay hints provider. - * - * Multiple providers can be registered for a language. In that case providers are asked in - * parallel and the results are merged. A failing provider (rejected promise or exception) will - * not cause a failure of the whole operation. - * - * @param selector A selector that defines the documents this provider is applicable to. - * @param provider An inlay hints provider. - * @return A {@link Disposable} that unregisters this provider when being disposed. - */ - export function registerInlayHintsProvider(selector: DocumentSelector, provider: InlayHintsProvider): Disposable; - } - - export enum InlayHintKind { - Other = 0, - Type = 1, - Parameter = 2, - } - - /** - * Inlay hint information. - */ - export class InlayHint { - /** - * The text of the hint. - */ - // todo@API label? - text: string; - /** - * The position of this hint. - */ - position: Position; - /** - * The kind of this hint. - */ - kind?: InlayHintKind; - /** - * Whitespace before the hint. - */ - whitespaceBefore?: boolean; - /** - * Whitespace after the hint. - */ - whitespaceAfter?: boolean; - - // todo@API make range first argument - constructor(text: string, position: Position, kind?: InlayHintKind); - } - - /** - * The inlay hints provider interface defines the contract between extensions and - * the inlay hints feature. - */ - export interface InlayHintsProvider { - - /** - * An optional event to signal that inlay hints have changed. - * @see {@link EventEmitter} - */ - //todo@API needs proper doc (like others) - onDidChangeInlayHints?: Event; - - /** - * - * @param model The document in which the command was invoked. - * @param range The range for which inlay hints should be computed. - * @param token A cancellation token. - * @return A list of inlay hints or a thenable that resolves to such. - */ - provideInlayHints(model: TextDocument, range: Range, token: CancellationToken): ProviderResult; - } - //#endregion - - //#region extensionRuntime: https://github.com/microsoft/vscode/issues/104436 - - export enum ExtensionRuntime { - /** - * The extension is running in a NodeJS extension host. Runtime access to NodeJS APIs is available. - */ - Node = 1, - /** - * The extension is running in a Webworker extension host. Runtime access is limited to Webworker APIs. - */ - Webworker = 2 - } - - export interface ExtensionContext { - readonly extensionRuntime: ExtensionRuntime; - } - - //#endregion - - //#region textDocumentNotebook: https://github.com/microsoft/vscode/issues/102091 - - export interface TextDocument { - - /** - * The {@link NotebookDocument notebook} that contains this document as a notebook cell or `undefined` when - * the document is not contained by a notebook (this should be the more frequent case). - */ - notebook: NotebookDocument | undefined; - } - //#endregion - - //#region testObserver: https://github.com/microsoft/vscode/issues/107467 - export namespace tests { - /** - * Requests that tests be run by their controller. - * @param run Run options to use. - * @param token Cancellation token for the test run - */ - export function runTests(run: TestRunRequest, token?: CancellationToken): Thenable; - - /** - * Returns an observer that watches and can request tests. - */ - export function createTestObserver(): TestObserver; - /** - * List of test results stored by the editor, sorted in descending - * order by their `completedAt` time. - */ - export const testResults: ReadonlyArray; - - /** - * Event that fires when the {@link testResults} array is updated. - */ - export const onDidChangeTestResults: Event; - } - - export interface TestObserver { - /** - * List of tests returned by test provider for files in the workspace. - */ - readonly tests: ReadonlyArray; - - /** - * An event that fires when an existing test in the collection changes, or - * null if a top-level test was added or removed. When fired, the consumer - * should check the test item and all its children for changes. - */ - readonly onDidChangeTest: Event; - - /** - * Dispose of the observer, allowing the editor to eventually tell test - * providers that they no longer need to update tests. - */ - dispose(): void; - } - - export interface TestsChangeEvent { - /** - * List of all tests that are newly added. - */ - readonly added: ReadonlyArray; - - /** - * List of existing tests that have updated. - */ - readonly updated: ReadonlyArray; - - /** - * List of existing tests that have been removed. - */ - readonly removed: ReadonlyArray; - } - - /** - * A test item is an item shown in the "test explorer" view. It encompasses - * both a suite and a test, since they have almost or identical capabilities. - */ - export interface TestItem { - /** - * Marks the test as outdated. This can happen as a result of file changes, - * for example. In "auto run" mode, tests that are outdated will be - * automatically rerun after a short delay. Invoking this on a - * test with children will mark the entire subtree as outdated. - * - * Extensions should generally not override this method. - */ - // todo@api still unsure about this - invalidateResults(): void; - } - - - /** - * TestResults can be provided to the editor in {@link tests.publishTestResult}, - * or read from it in {@link tests.testResults}. - * - * The results contain a 'snapshot' of the tests at the point when the test - * run is complete. Therefore, information such as its {@link Range} may be - * out of date. If the test still exists in the workspace, consumers can use - * its `id` to correlate the result instance with the living test. - */ - export interface TestRunResult { - /** - * Unix milliseconds timestamp at which the test run was completed. - */ - readonly completedAt: number; - - /** - * Optional raw output from the test run. - */ - readonly output?: string; - - /** - * List of test results. The items in this array are the items that - * were passed in the {@link tests.runTests} method. - */ - readonly results: ReadonlyArray>; - } - - /** - * A {@link TestItem}-like interface with an associated result, which appear - * or can be provided in {@link TestResult} interfaces. - */ - export interface TestResultSnapshot { - /** - * Unique identifier that matches that of the associated TestItem. - * This is used to correlate test results and tests in the document with - * those in the workspace (test explorer). - */ - readonly id: string; - - /** - * Parent of this item. - */ - readonly parent?: TestResultSnapshot; - - /** - * URI this TestItem is associated with. May be a file or file. - */ - readonly uri?: Uri; - - /** - * Display name describing the test case. - */ - readonly label: string; - - /** - * Optional description that appears next to the label. - */ - readonly description?: string; - - /** - * Location of the test item in its `uri`. This is only meaningful if the - * `uri` points to a file. - */ - readonly range?: Range; - - /** - * State of the test in each task. In the common case, a test will only - * be executed in a single task and the length of this array will be 1. - */ - readonly taskStates: ReadonlyArray; - - /** - * Optional list of nested tests for this item. - */ - readonly children: Readonly[]; - } - - export interface TestSnapshotTaskState { - /** - * Current result of the test. - */ - readonly state: TestResultState; - - /** - * The number of milliseconds the test took to run. This is set once the - * `state` is `Passed`, `Failed`, or `Errored`. - */ - readonly duration?: number; - - /** - * Associated test run message. Can, for example, contain assertion - * failure information if the test fails. - */ - readonly messages: ReadonlyArray; - } - - /** - * Possible states of tests in a test run. - */ - export enum TestResultState { - // Test will be run, but is not currently running. - Queued = 1, - // Test is currently running - Running = 2, - // Test run has passed - Passed = 3, - // Test run has failed (on an assertion) - Failed = 4, - // Test run has been skipped - Skipped = 5, - // Test run failed for some other reason (compilation error, timeout, etc) - Errored = 6 - } - - //#endregion - - //#region externalUriOpener: https://github.com/microsoft/vscode/issues/109277 - - /** - * Details if an `ExternalUriOpener` can open a uri. - * - * The priority is also used to rank multiple openers against each other and determine - * if an opener should be selected automatically or if the user should be prompted to - * select an opener. - * - * The editor will try to use the best available opener, as sorted by `ExternalUriOpenerPriority`. - * If there are multiple potential "best" openers for a URI, then the user will be prompted - * to select an opener. - */ - export enum ExternalUriOpenerPriority { - /** - * The opener is disabled and will never be shown to users. - * - * Note that the opener can still be used if the user specifically - * configures it in their settings. - */ - None = 0, - - /** - * The opener can open the uri but will not cause a prompt on its own - * since the editor always contributes a built-in `Default` opener. - */ - Option = 1, - - /** - * The opener can open the uri. - * - * The editor's built-in opener has `Default` priority. This means that any additional `Default` - * openers will cause the user to be prompted to select from a list of all potential openers. - */ - Default = 2, - - /** - * The opener can open the uri and should be automatically selected over any - * default openers, include the built-in one from the editor. - * - * A preferred opener will be automatically selected if no other preferred openers - * are available. If multiple preferred openers are available, then the user - * is shown a prompt with all potential openers (not just preferred openers). - */ - Preferred = 3, - } - - /** - * Handles opening uris to external resources, such as http(s) links. - * - * Extensions can implement an `ExternalUriOpener` to open `http` links to a webserver - * inside of the editor instead of having the link be opened by the web browser. - * - * Currently openers may only be registered for `http` and `https` uris. - */ - export interface ExternalUriOpener { - - /** - * Check if the opener can open a uri. - * - * @param uri The uri being opened. This is the uri that the user clicked on. It has - * not yet gone through port forwarding. - * @param token Cancellation token indicating that the result is no longer needed. - * - * @return Priority indicating if the opener can open the external uri. - */ - canOpenExternalUri(uri: Uri, token: CancellationToken): ExternalUriOpenerPriority | Thenable; - - /** - * Open a uri. - * - * This is invoked when: - * - * - The user clicks a link which does not have an assigned opener. In this case, first `canOpenExternalUri` - * is called and if the user selects this opener, then `openExternalUri` is called. - * - The user sets the default opener for a link in their settings and then visits a link. - * - * @param resolvedUri The uri to open. This uri may have been transformed by port forwarding, so it - * may not match the original uri passed to `canOpenExternalUri`. Use `ctx.originalUri` to check the - * original uri. - * @param ctx Additional information about the uri being opened. - * @param token Cancellation token indicating that opening has been canceled. - * - * @return Thenable indicating that the opening has completed. - */ - openExternalUri(resolvedUri: Uri, ctx: OpenExternalUriContext, token: CancellationToken): Thenable | void; - } - - /** - * Additional information about the uri being opened. - */ - interface OpenExternalUriContext { - /** - * The uri that triggered the open. - * - * This is the original uri that the user clicked on or that was passed to `openExternal.` - * Due to port forwarding, this may not match the `resolvedUri` passed to `openExternalUri`. - */ - readonly sourceUri: Uri; - } - - /** - * Additional metadata about a registered `ExternalUriOpener`. - */ - interface ExternalUriOpenerMetadata { - - /** - * List of uri schemes the opener is triggered for. - * - * Currently only `http` and `https` are supported. - */ - readonly schemes: readonly string[] - - /** - * Text displayed to the user that explains what the opener does. - * - * For example, 'Open in browser preview' - */ - readonly label: string; - } - - namespace window { - /** - * Register a new `ExternalUriOpener`. - * - * When a uri is about to be opened, an `onOpenExternalUri:SCHEME` activation event is fired. - * - * @param id Unique id of the opener, such as `myExtension.browserPreview`. This is used in settings - * and commands to identify the opener. - * @param opener Opener to register. - * @param metadata Additional information about the opener. - * - * @returns Disposable that unregisters the opener. - */ - export function registerExternalUriOpener(id: string, opener: ExternalUriOpener, metadata: ExternalUriOpenerMetadata): Disposable; - } - - interface OpenExternalOptions { - /** - * Allows using openers contributed by extensions through `registerExternalUriOpener` - * when opening the resource. - * - * If `true`, the editor will check if any contributed openers can handle the - * uri, and fallback to the default opener behavior. - * - * If it is string, this specifies the id of the `ExternalUriOpener` - * that should be used if it is available. Use `'default'` to force the editor's - * standard external opener to be used. - */ - readonly allowContributedOpeners?: boolean | string; - } - - namespace env { - export function openExternal(target: Uri, options?: OpenExternalOptions): Thenable; - } - - //#endregion - - //#region tabs: https://github.com/Microsoft/vscode/issues/15178 - - /** - * Represents a tab within the window - */ - export interface Tab { - /** - * The text displayed on the tab - */ - readonly label: string; - - /** - * The index of the tab within the column - */ - readonly index: number; - - /** - * The column which the tab belongs to - */ - readonly viewColumn: ViewColumn; - - /** - * The resource represented by the tab if available. - * Note: Not all tabs have a resource associated with them. - */ - readonly resource: Uri | undefined; - - /** - * The identifier of the view contained in the tab - * This is equivalent to `viewType` for custom editors and `notebookType` for notebooks. - * The built-in text editor has an id of 'default' for all configurations. - */ - readonly viewId: string | undefined; - - /** - * All the resources and viewIds represented by a tab - * {@link Tab.resource resource} and {@link Tab.viewId viewId} will - * always be at index 0. - */ - readonly additionalResourcesAndViewIds: readonly { - readonly resource: Uri | undefined, - readonly viewId: string | undefined - }[]; - - /** - * Whether or not the tab is currently active - * Dictated by being the selected tab in the active group - */ - readonly isActive: boolean; - - /** - * Moves a tab to the given index within the column. - * If the index is out of range, the tab will be moved to the end of the column. - * If the column is out of range, a new one will be created after the last existing column. - * @param index The index to move the tab to - * @param viewColumn The column to move the tab into - */ - move(index: number, viewColumn: ViewColumn): Thenable; - - /** - * Closes the tab. This makes the tab object invalid and the tab - * should no longer be used for further actions. - */ - close(): Thenable; - } - - export namespace window { - /** - * A list of all opened tabs - * Ordered from left to right - */ - export const tabs: readonly Tab[]; - - /** - * The currently active tab - * Undefined if no tabs are currently opened - */ - export const activeTab: Tab | undefined; - - /** - * An {@link Event} which fires when the array of {@link window.tabs tabs} - * has changed. - */ - export const onDidChangeTabs: Event; - - /** - * An {@link Event} which fires when the {@link window.activeTab activeTab} - * has changed. - */ - export const onDidChangeActiveTab: Event; - - } - - //#endregion - - //#region workspaceTrust: https://github.com/microsoft/vscode/issues/120173 - /** - * The object describing the properties of the workspace trust request - */ - export interface WorkspaceTrustRequestOptions { - /** - * Custom message describing the user action that requires workspace - * trust. If omitted, a generic message will be displayed in the workspace - * trust request dialog. - */ - readonly message?: string; - } - - export namespace workspace { - /** - * Prompt the user to chose whether to trust the current workspace - * @param options Optional object describing the properties of the - * workspace trust request. - */ - export function requestWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Thenable; - } - - //#endregion - - //#region portAttributesProvider: https://github.com/microsoft/vscode/issues/115616 @alexr00 - export enum PortAutoForwardAction { - Notify = 1, - OpenBrowser = 2, - OpenPreview = 3, - Silent = 4, - Ignore = 5, - OpenBrowserOnce = 6 - } - - export class PortAttributes { - /** - * The port number associated with this this set of attributes. - */ - port: number; - - /** - * The action to be taken when this port is detected for auto forwarding. - */ - autoForwardAction: PortAutoForwardAction; - - /** - * Creates a new PortAttributes object - * @param port the port number - * @param autoForwardAction the action to take when this port is detected - */ - constructor(port: number, autoForwardAction: PortAutoForwardAction); - } - - export interface PortAttributesProvider { - /** - * Provides attributes for the given port. For ports that your extension doesn't know about, simply - * return undefined. For example, if `providePortAttributes` is called with ports 3000 but your - * extension doesn't know anything about 3000 you should return undefined. - */ - providePortAttributes(port: number, pid: number | undefined, commandLine: string | undefined, token: CancellationToken): ProviderResult; - } - - export namespace workspace { - /** - * If your extension listens on ports, consider registering a PortAttributesProvider to provide information - * about the ports. For example, a debug extension may know about debug ports in it's debuggee. By providing - * this information with a PortAttributesProvider the extension can tell the editor that these ports should be - * ignored, since they don't need to be user facing. - * - * @param portSelector If registerPortAttributesProvider is called after you start your process then you may already - * know the range of ports or the pid of your process. All properties of a the portSelector must be true for your - * provider to get called. - * The `portRange` is start inclusive and end exclusive. - * @param provider The PortAttributesProvider - */ - export function registerPortAttributesProvider(portSelector: { pid?: number, portRange?: [number, number], commandMatcher?: RegExp }, provider: PortAttributesProvider): Disposable; - } - //#endregion - - //#region inlineCompletionProvider: https://github.com/microsoft/vscode/issues/124024 @hediet @alexdima - - export namespace languages { - /** - * Registers an inline completion provider. - */ - export function registerInlineCompletionItemProvider(selector: DocumentSelector, provider: InlineCompletionItemProvider): Disposable; - } - - export interface InlineCompletionItemProvider { - /** - * Provides inline completion items for the given position and document. - * If inline completions are enabled, this method will be called whenever the user stopped typing. - * It will also be called when the user explicitly triggers inline completions or asks for the next or previous inline completion. - * Use `context.triggerKind` to distinguish between these scenarios. - */ - provideInlineCompletionItems(document: TextDocument, position: Position, context: InlineCompletionContext, token: CancellationToken): ProviderResult | T[]>; - } - - export interface InlineCompletionContext { - /** - * How the completion was triggered. - */ - readonly triggerKind: InlineCompletionTriggerKind; - - /** - * Provides information about the currently selected item in the autocomplete widget if it is visible. - * - * If set, provided inline completions must extend the text of the selected item - * and use the same range, otherwise they are not shown as preview. - * As an example, if the document text is `console.` and the selected item is `.log` replacing the `.` in the document, - * the inline completion must also replace `.` and start with `.log`, for example `.log()`. - * - * Inline completion providers are requested again whenever the selected item changes. - * - * The user must configure `"editor.suggest.preview": true` for this feature. - */ - readonly selectedCompletionInfo: SelectedCompletionInfo | undefined; - } - - export interface SelectedCompletionInfo { - range: Range; - text: string; - } - - /** - * How an {@link InlineCompletionItemProvider inline completion provider} was triggered. - */ - export enum InlineCompletionTriggerKind { - /** - * Completion was triggered automatically while editing. - * It is sufficient to return a single completion item in this case. - */ - Automatic = 0, - - /** - * Completion was triggered explicitly by a user gesture. - * Return multiple completion items to enable cycling through them. - */ - Explicit = 1, - } - - export class InlineCompletionList { - items: T[]; - - constructor(items: T[]); - } - - export class InlineCompletionItem { - /** - * The text to replace the range with. - * - * The text the range refers to should be a prefix of this value and must be a subword (`AB` and `BEF` are subwords of `ABCDEF`, but `Ab` is not). - */ - text: string; - - /** - * The range to replace. - * Must begin and end on the same line. - * - * Prefer replacements over insertions to avoid cache invalidation: - * Instead of reporting a completion that inserts an extension at the end of a word, - * the whole word should be replaced with the extended word. - */ - range?: Range; - - /** - * An optional {@link Command} that is executed *after* inserting this completion. - */ - command?: Command; - - constructor(text: string, range?: Range, command?: Command); - } - - - /** - * Be aware that this API will not ever be finalized. - */ - export namespace window { - export function getInlineCompletionItemController(provider: InlineCompletionItemProvider): InlineCompletionController; - } - - /** - * Be aware that this API will not ever be finalized. - */ - export interface InlineCompletionController { - /** - * Is fired when an inline completion item is shown to the user. - */ - // eslint-disable-next-line vscode-dts-event-naming - readonly onDidShowCompletionItem: Event>; - } - - /** - * Be aware that this API will not ever be finalized. - */ - export interface InlineCompletionItemDidShowEvent { - completionItem: T; - } - - //#endregion - - //#region notebookMime: https://github.com/microsoft/vscode/issues/126280 @mjbvz - - export interface NotebookCellData { - /** - * Mime type determines how the cell's `value` is interpreted. - * - * The mime selects which notebook renders is used to render the cell. - * - * If not set, internally the cell is treated as having a mime type of `text/plain`. - * Cells that set `language` to `markdown` instead are treated as `text/markdown`. - */ - mime?: string; - } - - export interface NotebookCell { - /** - * Mime type determines how the markup cell's `value` is interpreted. - * - * The mime selects which notebook renders is used to render the cell. - * - * If not set, internally the cell is treated as having a mime type of `text/plain`. - * Cells that set `language` to `markdown` instead are treated as `text/markdown`. - */ - mime: string | undefined; - } - - //#endregion - - //#region testCoverage: https://github.com/microsoft/vscode/issues/123713 @connor4312 - export interface TestRun { - /** - * Test coverage provider for this result. An extension can defer setting - * this until after a run is complete and coverage is available. - */ - coverageProvider?: TestCoverageProvider - // ... - } - - /** - * Provides information about test coverage for a test result. - * Methods on the provider will not be called until the test run is complete - */ - export interface TestCoverageProvider { - /** - * Returns coverage information for all files involved in the test run. - * @param token A cancellation token. - * @return Coverage metadata for all files involved in the test. - */ - provideFileCoverage(token: CancellationToken): ProviderResult; - - /** - * Give a FileCoverage to fill in more data, namely {@link FileCoverage.detailedCoverage}. - * The editor will only resolve a FileCoverage once, and onyl if detailedCoverage - * is undefined. - * - * @param coverage A coverage object obtained from {@link provideFileCoverage} - * @param token A cancellation token. - * @return The resolved file coverage, or a thenable that resolves to one. It - * is OK to return the given `coverage`. When no result is returned, the - * given `coverage` will be used. - */ - resolveFileCoverage?(coverage: T, token: CancellationToken): ProviderResult; - } - - /** - * A class that contains information about a covered resource. A count can - * be give for lines, branches, and functions in a file. - */ - export class CoveredCount { - /** - * Number of items covered in the file. - */ - covered: number; - /** - * Total number of covered items in the file. - */ - total: number; - - /** - * @param covered Value for {@link CovereredCount.covered} - * @param total Value for {@link CovereredCount.total} - */ - constructor(covered: number, total: number); - } - - /** - * Contains coverage metadata for a file. - */ - export class FileCoverage { - /** - * File URI. - */ - readonly uri: Uri; - - /** - * Statement coverage information. If the reporter does not provide statement - * coverage information, this can instead be used to represent line coverage. - */ - statementCoverage: CoveredCount; - - /** - * Branch coverage information. - */ - branchCoverage?: CoveredCount; - - /** - * Function coverage information. - */ - functionCoverage?: CoveredCount; - - /** - * Detailed, per-statement coverage. If this is undefined, the editor will - * call {@link TestCoverageProvider.resolveFileCoverage} when necessary. - */ - detailedCoverage?: DetailedCoverage[]; - - /** - * Creates a {@link FileCoverage} instance with counts filled in from - * the coverage details. - * @param uri Covered file URI - * @param detailed Detailed coverage information - */ - static fromDetails(uri: Uri, details: readonly DetailedCoverage[]): FileCoverage; - - /** - * @param uri Covered file URI - * @param statementCoverage Statement coverage information. If the reporter - * does not provide statement coverage information, this can instead be - * used to represent line coverage. - * @param branchCoverage Branch coverage information - * @param functionCoverage Function coverage information - */ - constructor( - uri: Uri, - statementCoverage: CoveredCount, - branchCoverage?: CoveredCount, - functionCoverage?: CoveredCount, - ); - } - - /** - * Contains coverage information for a single statement or line. - */ - export class StatementCoverage { - /** - * The number of times this statement was executed. If zero, the - * statement will be marked as un-covered. - */ - executionCount: number; - - /** - * Statement location. - */ - location: Position | Range; - - /** - * Coverage from branches of this line or statement. If it's not a - * conditional, this will be empty. - */ - branches: BranchCoverage[]; - - /** - * @param location The statement position. - * @param executionCount The number of times this statement was - * executed. If zero, the statement will be marked as un-covered. - * @param branches Coverage from branches of this line. If it's not a - * conditional, this should be omitted. - */ - constructor(executionCount: number, location: Position | Range, branches?: BranchCoverage[]); - } - - /** - * Contains coverage information for a branch of a {@link StatementCoverage}. - */ - export class BranchCoverage { - /** - * The number of times this branch was executed. If zero, the - * branch will be marked as un-covered. - */ - executionCount: number; - - /** - * Branch location. - */ - location?: Position | Range; - - /** - * @param executionCount The number of times this branch was executed. - * @param location The branch position. - */ - constructor(executionCount: number, location?: Position | Range); - } - - /** - * Contains coverage information for a function or method. - */ - export class FunctionCoverage { - /** - * The number of times this function was executed. If zero, the - * function will be marked as un-covered. - */ - executionCount: number; - - /** - * Function location. - */ - location: Position | Range; - - /** - * @param executionCount The number of times this function was executed. - * @param location The function position. - */ - constructor(executionCount: number, location: Position | Range); - } - - export type DetailedCoverage = StatementCoverage | FunctionCoverage; - - //#endregion - - - - //#region scmActionButton: https://github.com/microsoft/vscode/issues/133935 - - export interface SourceControl { - actionButton?: Command; - } - - //#endregion - -} diff --git a/src/vscode-dts/vscode.proposed.diffCommand.d.ts b/src/vscode-dts/vscode.proposed.diffCommand.d.ts new file mode 100644 index 00000000000..84f4328e077 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.diffCommand.d.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/84899 + + /** + * The contiguous set of modified lines in a diff. + */ + export interface LineChange { + readonly originalStartLineNumber: number; + readonly originalEndLineNumber: number; + readonly modifiedStartLineNumber: number; + readonly modifiedEndLineNumber: number; + } + + export namespace commands { + + /** + * Registers a diff information command that can be invoked via a keyboard shortcut, + * a menu item, an action, or directly. + * + * Diff information commands are different from ordinary {@link commands.registerCommand commands} as + * they only execute when there is an active diff editor when the command is called, and the diff + * information has been computed. Also, the command handler of an editor command has access to + * the diff information. + * + * @param command A unique identifier for the command. + * @param callback A command handler function with access to the {@link LineChange diff information}. + * @param thisArg The `this` context used when invoking the handler function. + * @return Disposable which unregisters this command on disposal. + */ + export function registerDiffInformationCommand(command: string, callback: (diff: LineChange[], ...args: any[]) => any, thisArg?: any): Disposable; + } +} diff --git a/src/vscode-dts/vscode.proposed.documentFiltersExclusive.d.ts b/src/vscode-dts/vscode.proposed.documentFiltersExclusive.d.ts new file mode 100644 index 00000000000..7d033796355 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.documentFiltersExclusive.d.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // todo@jrieken add issue reference + + export interface DocumentFilter { + readonly exclusive?: boolean; + } +} diff --git a/src/vscode-dts/vscode.proposed.extensionRuntime.d.ts b/src/vscode-dts/vscode.proposed.extensionRuntime.d.ts new file mode 100644 index 00000000000..356587ebc95 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.extensionRuntime.d.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/104436 + + export enum ExtensionRuntime { + /** + * The extension is running in a NodeJS extension host. Runtime access to NodeJS APIs is available. + */ + Node = 1, + /** + * The extension is running in a Webworker extension host. Runtime access is limited to Webworker APIs. + */ + Webworker = 2 + } + + export interface ExtensionContext { + readonly extensionRuntime: ExtensionRuntime; + } +} diff --git a/src/vscode-dts/vscode.proposed.externalUriOpener.d.ts b/src/vscode-dts/vscode.proposed.externalUriOpener.d.ts new file mode 100644 index 00000000000..2b5432404f7 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.externalUriOpener.d.ts @@ -0,0 +1,163 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/109277 + + /** + * Details if an `ExternalUriOpener` can open a uri. + * + * The priority is also used to rank multiple openers against each other and determine + * if an opener should be selected automatically or if the user should be prompted to + * select an opener. + * + * The editor will try to use the best available opener, as sorted by `ExternalUriOpenerPriority`. + * If there are multiple potential "best" openers for a URI, then the user will be prompted + * to select an opener. + */ + export enum ExternalUriOpenerPriority { + /** + * The opener is disabled and will never be shown to users. + * + * Note that the opener can still be used if the user specifically + * configures it in their settings. + */ + None = 0, + + /** + * The opener can open the uri but will not cause a prompt on its own + * since the editor always contributes a built-in `Default` opener. + */ + Option = 1, + + /** + * The opener can open the uri. + * + * The editor's built-in opener has `Default` priority. This means that any additional `Default` + * openers will cause the user to be prompted to select from a list of all potential openers. + */ + Default = 2, + + /** + * The opener can open the uri and should be automatically selected over any + * default openers, include the built-in one from the editor. + * + * A preferred opener will be automatically selected if no other preferred openers + * are available. If multiple preferred openers are available, then the user + * is shown a prompt with all potential openers (not just preferred openers). + */ + Preferred = 3, + } + + /** + * Handles opening uris to external resources, such as http(s) links. + * + * Extensions can implement an `ExternalUriOpener` to open `http` links to a webserver + * inside of the editor instead of having the link be opened by the web browser. + * + * Currently openers may only be registered for `http` and `https` uris. + */ + export interface ExternalUriOpener { + + /** + * Check if the opener can open a uri. + * + * @param uri The uri being opened. This is the uri that the user clicked on. It has + * not yet gone through port forwarding. + * @param token Cancellation token indicating that the result is no longer needed. + * + * @return Priority indicating if the opener can open the external uri. + */ + canOpenExternalUri(uri: Uri, token: CancellationToken): ExternalUriOpenerPriority | Thenable; + + /** + * Open a uri. + * + * This is invoked when: + * + * - The user clicks a link which does not have an assigned opener. In this case, first `canOpenExternalUri` + * is called and if the user selects this opener, then `openExternalUri` is called. + * - The user sets the default opener for a link in their settings and then visits a link. + * + * @param resolvedUri The uri to open. This uri may have been transformed by port forwarding, so it + * may not match the original uri passed to `canOpenExternalUri`. Use `ctx.originalUri` to check the + * original uri. + * @param ctx Additional information about the uri being opened. + * @param token Cancellation token indicating that opening has been canceled. + * + * @return Thenable indicating that the opening has completed. + */ + openExternalUri(resolvedUri: Uri, ctx: OpenExternalUriContext, token: CancellationToken): Thenable | void; + } + + /** + * Additional information about the uri being opened. + */ + interface OpenExternalUriContext { + /** + * The uri that triggered the open. + * + * This is the original uri that the user clicked on or that was passed to `openExternal.` + * Due to port forwarding, this may not match the `resolvedUri` passed to `openExternalUri`. + */ + readonly sourceUri: Uri; + } + + /** + * Additional metadata about a registered `ExternalUriOpener`. + */ + interface ExternalUriOpenerMetadata { + + /** + * List of uri schemes the opener is triggered for. + * + * Currently only `http` and `https` are supported. + */ + readonly schemes: readonly string[] + + /** + * Text displayed to the user that explains what the opener does. + * + * For example, 'Open in browser preview' + */ + readonly label: string; + } + + namespace window { + /** + * Register a new `ExternalUriOpener`. + * + * When a uri is about to be opened, an `onOpenExternalUri:SCHEME` activation event is fired. + * + * @param id Unique id of the opener, such as `myExtension.browserPreview`. This is used in settings + * and commands to identify the opener. + * @param opener Opener to register. + * @param metadata Additional information about the opener. + * + * @returns Disposable that unregisters the opener. + */ + export function registerExternalUriOpener(id: string, opener: ExternalUriOpener, metadata: ExternalUriOpenerMetadata): Disposable; + } + + interface OpenExternalOptions { + /** + * Allows using openers contributed by extensions through `registerExternalUriOpener` + * when opening the resource. + * + * If `true`, the editor will check if any contributed openers can handle the + * uri, and fallback to the default opener behavior. + * + * If it is string, this specifies the id of the `ExternalUriOpener` + * that should be used if it is available. Use `'default'` to force the editor's + * standard external opener to be used. + */ + readonly allowContributedOpeners?: boolean | string; + } + + namespace env { + export function openExternal(target: Uri, options?: OpenExternalOptions): Thenable; + } +} diff --git a/src/vscode-dts/vscode.proposed.fileSearchProvider.d.ts b/src/vscode-dts/vscode.proposed.fileSearchProvider.d.ts new file mode 100644 index 00000000000..bf7bc5ecba3 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.fileSearchProvider.d.ts @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/73524 + + /** + * The parameters of a query for file search. + */ + export interface FileSearchQuery { + /** + * The search pattern to match against file paths. + */ + pattern: string; + } + + /** + * Options that apply to file search. + */ + export interface FileSearchOptions extends SearchOptions { + /** + * The maximum number of results to be returned. + */ + maxResults?: number; + + /** + * A CancellationToken that represents the session for this search query. If the provider chooses to, this object can be used as the key for a cache, + * and searches with the same session object can search the same cache. When the token is cancelled, the session is complete and the cache can be cleared. + */ + session?: CancellationToken; + } + + /** + * A FileSearchProvider provides search results for files in the given folder that match a query string. It can be invoked by quickopen or other extensions. + * + * A FileSearchProvider is the more powerful of two ways to implement file search in the editor. Use a FileSearchProvider if you wish to search within a folder for + * all files that match the user's query. + * + * The FileSearchProvider will be invoked on every keypress in quickopen. When `workspace.findFiles` is called, it will be invoked with an empty query string, + * and in that case, every file in the folder should be returned. + */ + export interface FileSearchProvider { + /** + * Provide the set of files that match a certain file path pattern. + * @param query The parameters for this query. + * @param options A set of options to consider while searching files. + * @param token A cancellation token. + */ + provideFileSearchResults(query: FileSearchQuery, options: FileSearchOptions, token: CancellationToken): ProviderResult; + } + + export namespace workspace { + /** + * Register a search provider. + * + * Only one provider can be registered per scheme. + * + * @param scheme The provider will be invoked for workspace folders that have this file scheme. + * @param provider The provider. + * @return A {@link Disposable} that unregisters this provider when being disposed. + */ + export function registerFileSearchProvider(scheme: string, provider: FileSearchProvider): Disposable; + } +} diff --git a/src/vscode-dts/vscode.proposed.findTextInFiles.d.ts b/src/vscode-dts/vscode.proposed.findTextInFiles.d.ts new file mode 100644 index 00000000000..14cd44d2048 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.findTextInFiles.d.ts @@ -0,0 +1,98 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/59924 + + /** + * Options that can be set on a findTextInFiles search. + */ + export interface FindTextInFilesOptions { + /** + * A {@link GlobPattern glob pattern} that defines the files to search for. The glob pattern + * will be matched against the file paths of files relative to their workspace. Use a {@link RelativePattern relative pattern} + * to restrict the search results to a {@link WorkspaceFolder workspace folder}. + */ + include?: GlobPattern; + + /** + * A {@link GlobPattern glob pattern} that defines files and folders to exclude. The glob pattern + * will be matched against the file paths of resulting matches relative to their workspace. When `undefined`, default excludes will + * apply. + */ + exclude?: GlobPattern; + + /** + * Whether to use the default and user-configured excludes. Defaults to true. + */ + useDefaultExcludes?: boolean; + + /** + * The maximum number of results to search for + */ + maxResults?: number; + + /** + * Whether external files that exclude files, like .gitignore, should be respected. + * See the vscode setting `"search.useIgnoreFiles"`. + */ + useIgnoreFiles?: boolean; + + /** + * Whether global files that exclude files, like .gitignore, should be respected. + * See the vscode setting `"search.useGlobalIgnoreFiles"`. + */ + useGlobalIgnoreFiles?: boolean; + + /** + * Whether symlinks should be followed while searching. + * See the vscode setting `"search.followSymlinks"`. + */ + followSymlinks?: boolean; + + /** + * Interpret files using this encoding. + * See the vscode setting `"files.encoding"` + */ + encoding?: string; + + /** + * Options to specify the size of the result text preview. + */ + previewOptions?: TextSearchPreviewOptions; + + /** + * Number of lines of context to include before each match. + */ + beforeContext?: number; + + /** + * Number of lines of context to include after each match. + */ + afterContext?: number; + } + + export namespace workspace { + /** + * Search text in files across all {@link workspace.workspaceFolders workspace folders} in the workspace. + * @param query The query parameters for the search - the search string, whether it's case-sensitive, or a regex, or matches whole words. + * @param callback A callback, called for each result + * @param token A token that can be used to signal cancellation to the underlying search engine. + * @return A thenable that resolves when the search is complete. + */ + export function findTextInFiles(query: TextSearchQuery, callback: (result: TextSearchResult) => void, token?: CancellationToken): Thenable; + + /** + * Search text in files across all {@link workspace.workspaceFolders workspace folders} in the workspace. + * @param query The query parameters for the search - the search string, whether it's case-sensitive, or a regex, or matches whole words. + * @param options An optional set of query options. Include and exclude patterns, maxResults, etc. + * @param callback A callback, called for each result + * @param token A token that can be used to signal cancellation to the underlying search engine. + * @return A thenable that resolves when the search is complete. + */ + export function findTextInFiles(query: TextSearchQuery, options: FindTextInFilesOptions, callback: (result: TextSearchResult) => void, token?: CancellationToken): Thenable; + } +} diff --git a/src/vscode-dts/vscode.proposed.inlayHints.d.ts b/src/vscode-dts/vscode.proposed.inlayHints.d.ts new file mode 100644 index 00000000000..9b3a30a7e3f --- /dev/null +++ b/src/vscode-dts/vscode.proposed.inlayHints.d.ts @@ -0,0 +1,89 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/16221 + + // todo@API Split between Inlay- and OverlayHints (InlayHint are for a position, OverlayHints for a non-empty range) + // todo@API add "mini-markdown" for links and styles + // (done) remove description + // (done) rename to InlayHint + // (done) add InlayHintKind with type, argument, etc + + export namespace languages { + /** + * Register a inlay hints provider. + * + * Multiple providers can be registered for a language. In that case providers are asked in + * parallel and the results are merged. A failing provider (rejected promise or exception) will + * not cause a failure of the whole operation. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider An inlay hints provider. + * @return A {@link Disposable} that unregisters this provider when being disposed. + */ + export function registerInlayHintsProvider(selector: DocumentSelector, provider: InlayHintsProvider): Disposable; + } + + export enum InlayHintKind { + Other = 0, + Type = 1, + Parameter = 2, + } + + /** + * Inlay hint information. + */ + export class InlayHint { + /** + * The text of the hint. + */ + // todo@API label? + text: string; + /** + * The position of this hint. + */ + position: Position; + /** + * The kind of this hint. + */ + kind?: InlayHintKind; + /** + * Whitespace before the hint. + */ + whitespaceBefore?: boolean; + /** + * Whitespace after the hint. + */ + whitespaceAfter?: boolean; + + // todo@API make range first argument + constructor(text: string, position: Position, kind?: InlayHintKind); + } + + /** + * The inlay hints provider interface defines the contract between extensions and + * the inlay hints feature. + */ + export interface InlayHintsProvider { + + /** + * An optional event to signal that inlay hints have changed. + * @see {@link EventEmitter} + */ + //todo@API needs proper doc (like others) + onDidChangeInlayHints?: Event; + + /** + * + * @param model The document in which the command was invoked. + * @param range The range for which inlay hints should be computed. + * @param token A cancellation token. + * @return A list of inlay hints or a thenable that resolves to such. + */ + provideInlayHints(model: TextDocument, range: Range, token: CancellationToken): ProviderResult; + } +} diff --git a/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts b/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts new file mode 100644 index 00000000000..a232a6d07e2 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts @@ -0,0 +1,127 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/124024 @hediet @alexdima + + export namespace languages { + /** + * Registers an inline completion provider. + */ + export function registerInlineCompletionItemProvider(selector: DocumentSelector, provider: InlineCompletionItemProvider): Disposable; + } + + export interface InlineCompletionItemProvider { + /** + * Provides inline completion items for the given position and document. + * If inline completions are enabled, this method will be called whenever the user stopped typing. + * It will also be called when the user explicitly triggers inline completions or asks for the next or previous inline completion. + * Use `context.triggerKind` to distinguish between these scenarios. + */ + provideInlineCompletionItems(document: TextDocument, position: Position, context: InlineCompletionContext, token: CancellationToken): ProviderResult | T[]>; + } + + export interface InlineCompletionContext { + /** + * How the completion was triggered. + */ + readonly triggerKind: InlineCompletionTriggerKind; + + /** + * Provides information about the currently selected item in the autocomplete widget if it is visible. + * + * If set, provided inline completions must extend the text of the selected item + * and use the same range, otherwise they are not shown as preview. + * As an example, if the document text is `console.` and the selected item is `.log` replacing the `.` in the document, + * the inline completion must also replace `.` and start with `.log`, for example `.log()`. + * + * Inline completion providers are requested again whenever the selected item changes. + * + * The user must configure `"editor.suggest.preview": true` for this feature. + */ + readonly selectedCompletionInfo: SelectedCompletionInfo | undefined; + } + + export interface SelectedCompletionInfo { + range: Range; + text: string; + } + + /** + * How an {@link InlineCompletionItemProvider inline completion provider} was triggered. + */ + export enum InlineCompletionTriggerKind { + /** + * Completion was triggered automatically while editing. + * It is sufficient to return a single completion item in this case. + */ + Automatic = 0, + + /** + * Completion was triggered explicitly by a user gesture. + * Return multiple completion items to enable cycling through them. + */ + Explicit = 1, + } + + export class InlineCompletionList { + items: T[]; + + constructor(items: T[]); + } + + export class InlineCompletionItem { + /** + * The text to replace the range with. + * + * The text the range refers to should be a prefix of this value and must be a subword (`AB` and `BEF` are subwords of `ABCDEF`, but `Ab` is not). + */ + text: string; + + /** + * The range to replace. + * Must begin and end on the same line. + * + * Prefer replacements over insertions to avoid cache invalidation: + * Instead of reporting a completion that inserts an extension at the end of a word, + * the whole word should be replaced with the extended word. + */ + range?: Range; + + /** + * An optional {@link Command} that is executed *after* inserting this completion. + */ + command?: Command; + + constructor(text: string, range?: Range, command?: Command); + } + + + /** + * Be aware that this API will not ever be finalized. + */ + export namespace window { + export function getInlineCompletionItemController(provider: InlineCompletionItemProvider): InlineCompletionController; + } + + /** + * Be aware that this API will not ever be finalized. + */ + export interface InlineCompletionController { + /** + * Is fired when an inline completion item is shown to the user. + */ + // eslint-disable-next-line vscode-dts-event-naming + readonly onDidShowCompletionItem: Event>; + } + + /** + * Be aware that this API will not ever be finalized. + */ + export interface InlineCompletionItemDidShowEvent { + completionItem: T; + } +} diff --git a/src/vscode-dts/vscode.proposed.notebookCellExecutionState.d.ts b/src/vscode-dts/vscode.proposed.notebookCellExecutionState.d.ts new file mode 100644 index 00000000000..67e5c4b0fbf --- /dev/null +++ b/src/vscode-dts/vscode.proposed.notebookCellExecutionState.d.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/124970 + + /** + * The execution state of a notebook cell. + */ + export enum NotebookCellExecutionState { + /** + * The cell is idle. + */ + Idle = 1, + /** + * Execution for the cell is pending. + */ + Pending = 2, + /** + * The cell is currently executing. + */ + Executing = 3, + } + + /** + * An event describing a cell execution state change. + */ + export interface NotebookCellExecutionStateChangeEvent { + /** + * The {@link NotebookCell cell} for which the execution state has changed. + */ + readonly cell: NotebookCell; + + /** + * The new execution state of the cell. + */ + readonly state: NotebookCellExecutionState; + } + + export namespace notebooks { + + /** + * An {@link Event} which fires when the execution state of a cell has changed. + */ + // todo@API this is an event that is fired for a property that cells don't have and that makes me wonder + // how a correct consumer works, e.g the consumer could have been late and missed an event? + export const onDidChangeNotebookCellExecutionState: Event; + } +} diff --git a/src/vscode-dts/vscode.proposed.notebookConcatTextDocument.d.ts b/src/vscode-dts/vscode.proposed.notebookConcatTextDocument.d.ts new file mode 100644 index 00000000000..fd4e0e0b001 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.notebookConcatTextDocument.d.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/106744 + + export namespace notebooks { + /** + * Create a document that is the concatenation of all notebook cells. By default all code-cells are included + * but a selector can be provided to narrow to down the set of cells. + * + * @param notebook + * @param selector + */ + // todo@API really needed? we didn't find a user here + export function createConcatTextDocument(notebook: NotebookDocument, selector?: DocumentSelector): NotebookConcatTextDocument; + } + + export interface NotebookConcatTextDocument { + readonly uri: Uri; + readonly isClosed: boolean; + dispose(): void; + readonly onDidChange: Event; + readonly version: number; + getText(): string; + getText(range: Range): string; + + offsetAt(position: Position): number; + positionAt(offset: number): Position; + validateRange(range: Range): Range; + validatePosition(position: Position): Position; + + locationAt(positionOrRange: Position | Range): Location; + positionAt(location: Location): Position; + contains(uri: Uri): boolean; + } +} diff --git a/src/vscode-dts/vscode.proposed.notebookContentProvider.d.ts b/src/vscode-dts/vscode.proposed.notebookContentProvider.d.ts new file mode 100644 index 00000000000..d30fdb167f4 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.notebookContentProvider.d.ts @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/106744 + + interface NotebookDocumentBackup { + /** + * Unique identifier for the backup. + * + * This id is passed back to your extension in `openNotebook` when opening a notebook editor from a backup. + */ + readonly id: string; + + /** + * Delete the current backup. + * + * This is called by the editor when it is clear the current backup is no longer needed, such as when a new backup + * is made or when the file is saved. + */ + delete(): void; + } + + interface NotebookDocumentBackupContext { + readonly destination: Uri; + } + + interface NotebookDocumentOpenContext { + readonly backupId?: string; + readonly untitledDocumentData?: Uint8Array; + } + + // todo@API use openNotebookDOCUMENT to align with openCustomDocument etc? + // todo@API rename to NotebookDocumentContentProvider + export interface NotebookContentProvider { + + readonly options?: NotebookDocumentContentOptions; + readonly onDidChangeNotebookContentOptions?: Event; + + /** + * Content providers should always use {@link FileSystemProvider file system providers} to + * resolve the raw content for `uri` as the resouce is not necessarily a file on disk. + */ + openNotebook(uri: Uri, openContext: NotebookDocumentOpenContext, token: CancellationToken): NotebookData | Thenable; + + // todo@API use NotebookData instead + saveNotebook(document: NotebookDocument, token: CancellationToken): Thenable; + + // todo@API use NotebookData instead + saveNotebookAs(targetResource: Uri, document: NotebookDocument, token: CancellationToken): Thenable; + + // todo@API use NotebookData instead + backupNotebook(document: NotebookDocument, context: NotebookDocumentBackupContext, token: CancellationToken): Thenable; + } + + export namespace workspace { + + // TODO@api use NotebookDocumentFilter instead of just notebookType:string? + // TODO@API options duplicates the more powerful variant on NotebookContentProvider + export function registerNotebookContentProvider(notebookType: string, provider: NotebookContentProvider, options?: NotebookDocumentContentOptions): Disposable; + } +} diff --git a/src/vscode-dts/vscode.proposed.notebookControllerKind.d.ts b/src/vscode-dts/vscode.proposed.notebookControllerKind.d.ts new file mode 100644 index 00000000000..b8a4a5376e8 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.notebookControllerKind.d.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode-jupyter/issues/7373 + + export interface NotebookController { + /** + * The human-readable label used to categorise controllers. + */ + kind?: string; + } +} diff --git a/src/vscode-dts/vscode.proposed.notebookDebugOptions.d.ts b/src/vscode-dts/vscode.proposed.notebookDebugOptions.d.ts new file mode 100644 index 00000000000..6bd35115a81 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.notebookDebugOptions.d.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // eslint-disable-next-line vscode-dts-region-comments + // @roblourens: new debug session option for simple UI 'managedByParent' (see https://github.com/microsoft/vscode/issues/128588) + + /** + * Options for {@link debug.startDebugging starting a debug session}. + */ + export interface DebugSessionOptions { + + debugUI?: { + /** + * When true, the debug toolbar will not be shown for this session, the window statusbar color will not be changed, and the debug viewlet will not be automatically revealed. + */ + simple?: boolean; + } + + /** + * When true, a save will not be triggered for open editors when starting a debug session, regardless of the value of the `debug.saveBeforeStart` setting. + */ + suppressSaveBeforeStart?: boolean; + } +} diff --git a/src/vscode-dts/vscode.proposed.notebookDeprecated.d.ts b/src/vscode-dts/vscode.proposed.notebookDeprecated.d.ts new file mode 100644 index 00000000000..c9ac1073772 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.notebookDeprecated.d.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/106744 + + export interface NotebookCellOutput { + id: string; + } +} diff --git a/src/vscode-dts/vscode.proposed.notebookEditor.d.ts b/src/vscode-dts/vscode.proposed.notebookEditor.d.ts new file mode 100644 index 00000000000..0181b8f7ef2 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.notebookEditor.d.ts @@ -0,0 +1,170 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/106744 + + /** + * Represents a notebook editor that is attached to a {@link NotebookDocument notebook}. + */ + export enum NotebookEditorRevealType { + /** + * The range will be revealed with as little scrolling as possible. + */ + Default = 0, + + /** + * The range will always be revealed in the center of the viewport. + */ + InCenter = 1, + + /** + * If the range is outside the viewport, it will be revealed in the center of the viewport. + * Otherwise, it will be revealed with as little scrolling as possible. + */ + InCenterIfOutsideViewport = 2, + + /** + * The range will always be revealed at the top of the viewport. + */ + AtTop = 3 + } + + /** + * Represents a notebook editor that is attached to a {@link NotebookDocument notebook}. + */ + export interface NotebookEditor { + /** + * The document associated with this notebook editor. + */ + //todo@api rename to notebook? + readonly document: NotebookDocument; + + /** + * The selections on this notebook editor. + * + * The primary selection (or focused range) is `selections[0]`. When the document has no cells, the primary selection is empty `{ start: 0, end: 0 }`; + */ + selections: NotebookRange[]; + + /** + * The current visible ranges in the editor (vertically). + */ + readonly visibleRanges: NotebookRange[]; + + /** + * Scroll as indicated by `revealType` in order to reveal the given range. + * + * @param range A range. + * @param revealType The scrolling strategy for revealing `range`. + */ + revealRange(range: NotebookRange, revealType?: NotebookEditorRevealType): void; + + /** + * The column in which this editor shows. + */ + readonly viewColumn?: ViewColumn; + } + + export interface NotebookDocumentMetadataChangeEvent { + /** + * The {@link NotebookDocument notebook document} for which the document metadata have changed. + */ + //todo@API rename to notebook? + readonly document: NotebookDocument; + } + + export interface NotebookCellsChangeData { + readonly start: number; + // todo@API end? Use NotebookCellRange instead? + readonly deletedCount: number; + // todo@API removedCells, deletedCells? + readonly deletedItems: NotebookCell[]; + // todo@API addedCells, insertedCells, newCells? + readonly items: NotebookCell[]; + } + + export interface NotebookCellsChangeEvent { + /** + * The {@link NotebookDocument notebook document} for which the cells have changed. + */ + //todo@API rename to notebook? + readonly document: NotebookDocument; + readonly changes: ReadonlyArray; + } + + export interface NotebookCellOutputsChangeEvent { + /** + * The {@link NotebookDocument notebook document} for which the cell outputs have changed. + */ + //todo@API remove? use cell.notebook instead? + readonly document: NotebookDocument; + // NotebookCellOutputsChangeEvent.cells vs NotebookCellMetadataChangeEvent.cell + readonly cells: NotebookCell[]; + } + + export interface NotebookCellMetadataChangeEvent { + /** + * The {@link NotebookDocument notebook document} for which the cell metadata have changed. + */ + //todo@API remove? use cell.notebook instead? + readonly document: NotebookDocument; + // NotebookCellOutputsChangeEvent.cells vs NotebookCellMetadataChangeEvent.cell + readonly cell: NotebookCell; + } + + export interface NotebookEditorSelectionChangeEvent { + /** + * The {@link NotebookEditor notebook editor} for which the selections have changed. + */ + readonly notebookEditor: NotebookEditor; + readonly selections: ReadonlyArray + } + + export interface NotebookEditorVisibleRangesChangeEvent { + /** + * The {@link NotebookEditor notebook editor} for which the visible ranges have changed. + */ + readonly notebookEditor: NotebookEditor; + readonly visibleRanges: ReadonlyArray; + } + + + export interface NotebookDocumentShowOptions { + viewColumn?: ViewColumn; + preserveFocus?: boolean; + preview?: boolean; + selections?: NotebookRange[]; + } + + export namespace notebooks { + + + + export const onDidSaveNotebookDocument: Event; + + export const onDidChangeNotebookDocumentMetadata: Event; + export const onDidChangeNotebookCells: Event; + + // todo@API add onDidChangeNotebookCellOutputs + export const onDidChangeCellOutputs: Event; + + // todo@API add onDidChangeNotebookCellMetadata + export const onDidChangeCellMetadata: Event; + } + + export namespace window { + export const visibleNotebookEditors: NotebookEditor[]; + export const onDidChangeVisibleNotebookEditors: Event; + export const activeNotebookEditor: NotebookEditor | undefined; + export const onDidChangeActiveNotebookEditor: Event; + export const onDidChangeNotebookEditorSelection: Event; + export const onDidChangeNotebookEditorVisibleRanges: Event; + + export function showNotebookDocument(uri: Uri, options?: NotebookDocumentShowOptions): Thenable; + export function showNotebookDocument(document: NotebookDocument, options?: NotebookDocumentShowOptions): Thenable; + } +} diff --git a/src/vscode-dts/vscode.proposed.notebookEditorDecorationType.d.ts b/src/vscode-dts/vscode.proposed.notebookEditorDecorationType.d.ts new file mode 100644 index 00000000000..5a07e05ef78 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.notebookEditorDecorationType.d.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/106744 + + export interface NotebookEditor { + setDecorations(decorationType: NotebookEditorDecorationType, range: NotebookRange): void; + } + + export interface NotebookDecorationRenderOptions { + backgroundColor?: string | ThemeColor; + borderColor?: string | ThemeColor; + top?: ThemableDecorationAttachmentRenderOptions; + } + + export interface NotebookEditorDecorationType { + readonly key: string; + dispose(): void; + } + + export namespace notebooks { + export function createNotebookEditorDecorationType(options: NotebookDecorationRenderOptions): NotebookEditorDecorationType; + } +} diff --git a/src/vscode-dts/vscode.proposed.notebookEditorEdit.d.ts b/src/vscode-dts/vscode.proposed.notebookEditorEdit.d.ts new file mode 100644 index 00000000000..6c954b5f76c --- /dev/null +++ b/src/vscode-dts/vscode.proposed.notebookEditorEdit.d.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/106744 + + // todo@API add NotebookEdit-type which handles all these cases? + // export class NotebookEdit { + // range: NotebookRange; + // newCells: NotebookCellData[]; + // newMetadata?: NotebookDocumentMetadata; + // constructor(range: NotebookRange, newCells: NotebookCellData) + // } + + // export class NotebookCellEdit { + // newMetadata?: NotebookCellMetadata; + // } + + // export interface WorkspaceEdit { + // set(uri: Uri, edits: TextEdit[] | NotebookEdit[]): void + // } + + export interface WorkspaceEdit { + // todo@API add NotebookEdit-type which handles all these cases? + replaceNotebookMetadata(uri: Uri, value: { [key: string]: any }): void; + replaceNotebookCells(uri: Uri, range: NotebookRange, cells: NotebookCellData[], metadata?: WorkspaceEditEntryMetadata): void; + replaceNotebookCellMetadata(uri: Uri, index: number, cellMetadata: { [key: string]: any }, metadata?: WorkspaceEditEntryMetadata): void; + } + + export interface NotebookEditorEdit { + replaceMetadata(value: { [key: string]: any }): void; + replaceCells(start: number, end: number, cells: NotebookCellData[]): void; + replaceCellMetadata(index: number, metadata: { [key: string]: any }): void; + } + + export interface NotebookEditor { + /** + * Perform an edit on the notebook associated with this notebook editor. + * + * The given callback-function is invoked with an {@link NotebookEditorEdit edit-builder} which must + * be used to make edits. Note that the edit-builder is only valid while the + * callback executes. + * + * @param callback A function which can create edits using an {@link NotebookEditorEdit edit-builder}. + * @return A promise that resolves with a value indicating if the edits could be applied. + */ + // @jrieken REMOVE maybe + edit(callback: (editBuilder: NotebookEditorEdit) => void): Thenable; + } +} diff --git a/src/vscode-dts/vscode.proposed.notebookLiveShare.d.ts b/src/vscode-dts/vscode.proposed.notebookLiveShare.d.ts new file mode 100644 index 00000000000..de2966db16c --- /dev/null +++ b/src/vscode-dts/vscode.proposed.notebookLiveShare.d.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/106744 + + export interface NotebookRegistrationData { + displayName: string; + filenamePattern: (GlobPattern | { include: GlobPattern; exclude: GlobPattern; })[]; + exclusive?: boolean; + } + + export namespace workspace { + // SPECIAL overload with NotebookRegistrationData + export function registerNotebookContentProvider(notebookType: string, provider: NotebookContentProvider, options?: NotebookDocumentContentOptions, registrationData?: NotebookRegistrationData): Disposable; + // SPECIAL overload with NotebookRegistrationData + export function registerNotebookSerializer(notebookType: string, serializer: NotebookSerializer, options?: NotebookDocumentContentOptions, registration?: NotebookRegistrationData): Disposable; + } +} diff --git a/src/vscode-dts/vscode.proposed.notebookMessaging.d.ts b/src/vscode-dts/vscode.proposed.notebookMessaging.d.ts new file mode 100644 index 00000000000..88660ded2e6 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.notebookMessaging.d.ts @@ -0,0 +1,68 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/123601 + + /** + * Represents a script that is loaded into the notebook renderer before rendering output. This allows + * to provide and share functionality for notebook markup and notebook output renderers. + */ + export class NotebookRendererScript { + + /** + * APIs that the preload provides to the renderer. These are matched + * against the `dependencies` and `optionalDependencies` arrays in the + * notebook renderer contribution point. + */ + provides: string[]; + + /** + * URI of the JavaScript module to preload. + * + * This module must export an `activate` function that takes a context object that contains the notebook API. + */ + uri: Uri; + + /** + * @param uri URI of the JavaScript module to preload + * @param provides Value for the `provides` property + */ + constructor(uri: Uri, provides?: string | string[]); + } + + export interface NotebookController { + + // todo@API allow add, not remove + readonly rendererScripts: NotebookRendererScript[]; + + /** + * An event that fires when a {@link NotebookController.rendererScripts renderer script} has send a message to + * the controller. + */ + readonly onDidReceiveMessage: Event<{ editor: NotebookEditor, message: any }>; + + /** + * Send a message to the renderer of notebook editors. + * + * Note that only editors showing documents that are bound to this controller + * are receiving the message. + * + * @param message The message to send. + * @param editor A specific editor to send the message to. When `undefined` all applicable editors are receiving the message. + * @returns A promise that resolves to a boolean indicating if the message has been send or not. + */ + postMessage(message: any, editor?: NotebookEditor): Thenable; + + //todo@API validate this works + asWebviewUri(localResource: Uri): Uri; + } + + export namespace notebooks { + + export function createNotebookController(id: string, viewType: string, label: string, handler?: (cells: NotebookCell[], notebook: NotebookDocument, controller: NotebookController) => void | Thenable, rendererScripts?: NotebookRendererScript[]): NotebookController; + } +} diff --git a/src/vscode-dts/vscode.proposed.notebookMime.d.ts b/src/vscode-dts/vscode.proposed.notebookMime.d.ts new file mode 100644 index 00000000000..fb1cf089dcc --- /dev/null +++ b/src/vscode-dts/vscode.proposed.notebookMime.d.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/126280 @mjbvz + + export interface NotebookCellData { + /** + * Mime type determines how the cell's `value` is interpreted. + * + * The mime selects which notebook renders is used to render the cell. + * + * If not set, internally the cell is treated as having a mime type of `text/plain`. + * Cells that set `language` to `markdown` instead are treated as `text/markdown`. + */ + mime?: string; + } + + export interface NotebookCell { + /** + * Mime type determines how the markup cell's `value` is interpreted. + * + * The mime selects which notebook renders is used to render the cell. + * + * If not set, internally the cell is treated as having a mime type of `text/plain`. + * Cells that set `language` to `markdown` instead are treated as `text/markdown`. + */ + mime: string | undefined; + } +} diff --git a/src/vscode-dts/vscode.proposed.portsAttributes.d.ts b/src/vscode-dts/vscode.proposed.portsAttributes.d.ts new file mode 100644 index 00000000000..1842605a5e7 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.portsAttributes.d.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/115616 @alexr00 + + export enum PortAutoForwardAction { + Notify = 1, + OpenBrowser = 2, + OpenPreview = 3, + Silent = 4, + Ignore = 5, + OpenBrowserOnce = 6 + } + + export class PortAttributes { + /** + * The port number associated with this this set of attributes. + */ + port: number; + + /** + * The action to be taken when this port is detected for auto forwarding. + */ + autoForwardAction: PortAutoForwardAction; + + /** + * Creates a new PortAttributes object + * @param port the port number + * @param autoForwardAction the action to take when this port is detected + */ + constructor(port: number, autoForwardAction: PortAutoForwardAction); + } + + export interface PortAttributesProvider { + /** + * Provides attributes for the given port. For ports that your extension doesn't know about, simply + * return undefined. For example, if `providePortAttributes` is called with ports 3000 but your + * extension doesn't know anything about 3000 you should return undefined. + */ + providePortAttributes(port: number, pid: number | undefined, commandLine: string | undefined, token: CancellationToken): ProviderResult; + } + + export namespace workspace { + /** + * If your extension listens on ports, consider registering a PortAttributesProvider to provide information + * about the ports. For example, a debug extension may know about debug ports in it's debuggee. By providing + * this information with a PortAttributesProvider the extension can tell the editor that these ports should be + * ignored, since they don't need to be user facing. + * + * @param portSelector If registerPortAttributesProvider is called after you start your process then you may already + * know the range of ports or the pid of your process. All properties of a the portSelector must be true for your + * provider to get called. + * The `portRange` is start inclusive and end exclusive. + * @param provider The PortAttributesProvider + */ + export function registerPortAttributesProvider(portSelector: { pid?: number, portRange?: [number, number], commandMatcher?: RegExp }, provider: PortAttributesProvider): Disposable; + } +} diff --git a/src/vscode-dts/vscode.proposed.quickPickSeparators.d.ts b/src/vscode-dts/vscode.proposed.quickPickSeparators.d.ts new file mode 100644 index 00000000000..c73c37b2095 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.quickPickSeparators.d.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/74967 + + export enum QuickPickItemKind { + Default = 1, + Separator = 2, + } + + export interface QuickPickItem { + kind?: QuickPickItemKind + } +} diff --git a/src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts b/src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts new file mode 100644 index 00000000000..405d67671d7 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/73904 + + export interface QuickPick extends QuickInput { + /** + * An optional flag to sort the final results by index of first query match in label. Defaults to true. + */ + sortByLabel: boolean; + } +} diff --git a/src/vscode-dts/vscode.proposed.resolvers.d.ts b/src/vscode-dts/vscode.proposed.resolvers.d.ts new file mode 100644 index 00000000000..211c201028d --- /dev/null +++ b/src/vscode-dts/vscode.proposed.resolvers.d.ts @@ -0,0 +1,228 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + //resolvers: @alexdima + + export interface MessageOptions { + /** + * Do not render a native message box. + */ + useCustom?: boolean; + } + + export interface RemoteAuthorityResolverContext { + resolveAttempt: number; + } + + export class ResolvedAuthority { + readonly host: string; + readonly port: number; + readonly connectionToken: string | undefined; + + constructor(host: string, port: number, connectionToken?: string); + } + + export interface ResolvedOptions { + extensionHostEnv?: { [key: string]: string | null; }; + + isTrusted?: boolean; + + /** + * When provided, remote server will be initialized with the extensions synced using the given user account. + */ + authenticationSessionForInitializingExtensions?: AuthenticationSession & { providerId: string }; + } + + export interface TunnelPrivacy { + themeIcon: string; + id: string; + label: string; + } + + export interface TunnelOptions { + remoteAddress: { port: number, host: string; }; + // The desired local port. If this port can't be used, then another will be chosen. + localAddressPort?: number; + label?: string; + /** + * @deprecated Use privacy instead + */ + public?: boolean; + privacy?: string; + protocol?: string; + } + + export interface TunnelDescription { + remoteAddress: { port: number, host: string; }; + //The complete local address(ex. localhost:1234) + localAddress: { port: number, host: string; } | string; + /** + * @deprecated Use privacy instead + */ + public?: boolean; + privacy?: string; + // If protocol is not provided it is assumed to be http, regardless of the localAddress. + protocol?: string; + } + + export interface Tunnel extends TunnelDescription { + // Implementers of Tunnel should fire onDidDispose when dispose is called. + onDidDispose: Event; + dispose(): void | Thenable; + } + + /** + * Used as part of the ResolverResult if the extension has any candidate, + * published, or forwarded ports. + */ + export interface TunnelInformation { + /** + * Tunnels that are detected by the extension. The remotePort is used for display purposes. + * The localAddress should be the complete local address (ex. localhost:1234) for connecting to the port. Tunnels provided through + * detected are read-only from the forwarded ports UI. + */ + environmentTunnels?: TunnelDescription[]; + + } + + export interface TunnelCreationOptions { + /** + * True when the local operating system will require elevation to use the requested local port. + */ + elevationRequired?: boolean; + } + + export enum CandidatePortSource { + None = 0, + Process = 1, + Output = 2 + } + + export type ResolverResult = ResolvedAuthority & ResolvedOptions & TunnelInformation; + + export class RemoteAuthorityResolverError extends Error { + static NotAvailable(message?: string, handled?: boolean): RemoteAuthorityResolverError; + static TemporarilyNotAvailable(message?: string): RemoteAuthorityResolverError; + + constructor(message?: string); + } + + export interface RemoteAuthorityResolver { + /** + * Resolve the authority part of the current opened `vscode-remote://` URI. + * + * This method will be invoked once during the startup of the editor and again each time + * the editor detects a disconnection. + * + * @param authority The authority part of the current opened `vscode-remote://` URI. + * @param context A context indicating if this is the first call or a subsequent call. + */ + resolve(authority: string, context: RemoteAuthorityResolverContext): ResolverResult | Thenable; + + /** + * Get the canonical URI (if applicable) for a `vscode-remote://` URI. + * + * @returns The canonical URI or undefined if the uri is already canonical. + */ + getCanonicalURI?(uri: Uri): ProviderResult; + + /** + * Can be optionally implemented if the extension can forward ports better than the core. + * When not implemented, the core will use its default forwarding logic. + * When implemented, the core will use this to forward ports. + * + * To enable the "Change Local Port" action on forwarded ports, make sure to set the `localAddress` of + * the returned `Tunnel` to a `{ port: number, host: string; }` and not a string. + */ + tunnelFactory?: (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions) => Thenable | undefined; + + /**p + * Provides filtering for candidate ports. + */ + showCandidatePort?: (host: string, port: number, detail: string) => Thenable; + + /** + * Lets the resolver declare which tunnel factory features it supports. + * UNDER DISCUSSION! MAY CHANGE SOON. + */ + tunnelFeatures?: { + elevation: boolean; + /** + * @deprecated Use privacy instead + */ + public: boolean; + /** + * One of the the options must have the ID "private". + */ + privacyOptions: TunnelPrivacy[]; + }; + + candidatePortSource?: CandidatePortSource; + } + + export namespace workspace { + /** + * Forwards a port. If the current resolver implements RemoteAuthorityResolver:forwardPort then that will be used to make the tunnel. + * By default, openTunnel only support localhost; however, RemoteAuthorityResolver:tunnelFactory can be used to support other ips. + * + * @throws When run in an environment without a remote. + * + * @param tunnelOptions The `localPort` is a suggestion only. If that port is not available another will be chosen. + */ + export function openTunnel(tunnelOptions: TunnelOptions): Thenable; + + /** + * Gets an array of the currently available tunnels. This does not include environment tunnels, only tunnels that have been created by the user. + * Note that these are of type TunnelDescription and cannot be disposed. + */ + export let tunnels: Thenable; + + /** + * Fired when the list of tunnels has changed. + */ + export const onDidChangeTunnels: Event; + } + + export interface ResourceLabelFormatter { + scheme: string; + authority?: string; + formatting: ResourceLabelFormatting; + } + + export interface ResourceLabelFormatting { + label: string; // myLabel:/${path} + // For historic reasons we use an or string here. Once we finalize this API we should start using enums instead and adopt it in extensions. + // eslint-disable-next-line vscode-dts-literal-or-types + separator: '/' | '\\' | ''; + tildify?: boolean; + normalizeDriveLetter?: boolean; + workspaceSuffix?: string; + workspaceTooltip?: string; + authorityPrefix?: string; + stripPathStartingSeparator?: boolean; + } + + export namespace workspace { + export function registerRemoteAuthorityResolver(authorityPrefix: string, resolver: RemoteAuthorityResolver): Disposable; + export function registerResourceLabelFormatter(formatter: ResourceLabelFormatter): Disposable; + } + + export namespace env { + + /** + * The authority part of the current opened `vscode-remote://` URI. + * Defined by extensions, e.g. `ssh-remote+${host}` for remotes using a secure shell. + * + * *Note* that the value is `undefined` when there is no remote extension host but that the + * value is defined in all extension hosts (local and remote) in case a remote extension host + * exists. Use {@link Extension.extensionKind} to know if + * a specific extension runs remote or not. + */ + export const remoteAuthority: string | undefined; + + } +} diff --git a/src/vscode-dts/vscode.proposed.scmActionButton.d.ts b/src/vscode-dts/vscode.proposed.scmActionButton.d.ts new file mode 100644 index 00000000000..f60dc3047c7 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.scmActionButton.d.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + // https://github.com/microsoft/vscode/issues/133935 + + export interface SourceControl { + actionButton?: Command; + } +} diff --git a/src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts b/src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts new file mode 100644 index 00000000000..bced264e5a5 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // todo@joaomoreno add issue reference + + export interface SourceControl { + + /** + * Whether the source control is selected. + */ + readonly selected: boolean; + + /** + * An event signaling when the selection state changes. + */ + readonly onDidChangeSelection: Event; + } +} diff --git a/src/vscode-dts/vscode.proposed.scmValidation.d.ts b/src/vscode-dts/vscode.proposed.scmValidation.d.ts new file mode 100644 index 00000000000..a5fad56ebc2 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.scmValidation.d.ts @@ -0,0 +1,60 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // todo@joaomoreno add issue reference + + /** + * Represents the validation type of the Source Control input. + */ + export enum SourceControlInputBoxValidationType { + + /** + * Something not allowed by the rules of a language or other means. + */ + Error = 0, + + /** + * Something suspicious but allowed. + */ + Warning = 1, + + /** + * Something to inform about but not a problem. + */ + Information = 2 + } + + export interface SourceControlInputBoxValidation { + + /** + * The validation message to display. + */ + readonly message: string | MarkdownString; + + /** + * The validation type. + */ + readonly type: SourceControlInputBoxValidationType; + } + + /** + * Represents the input box in the Source Control viewlet. + */ + export interface SourceControlInputBox { + + /** + * Shows a transient contextual message on the input. + */ + showValidationMessage(message: string | MarkdownString, type: SourceControlInputBoxValidationType): void; + + /** + * A validation function for the input box. It's possible to change + * the validation provider simply by setting this property to a different function. + */ + validateInput?(value: string, cursorPosition: number): ProviderResult; + } +} diff --git a/src/vscode-dts/vscode.proposed.tabs.d.ts b/src/vscode-dts/vscode.proposed.tabs.d.ts new file mode 100644 index 00000000000..691cfd8f07c --- /dev/null +++ b/src/vscode-dts/vscode.proposed.tabs.d.ts @@ -0,0 +1,100 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/Microsoft/vscode/issues/15178 + + /** + * Represents a tab within the window + */ + export interface Tab { + /** + * The text displayed on the tab + */ + readonly label: string; + + /** + * The index of the tab within the column + */ + readonly index: number; + + /** + * The column which the tab belongs to + */ + readonly viewColumn: ViewColumn; + + /** + * The resource represented by the tab if available. + * Note: Not all tabs have a resource associated with them. + */ + readonly resource: Uri | undefined; + + /** + * The identifier of the view contained in the tab + * This is equivalent to `viewType` for custom editors and `notebookType` for notebooks. + * The built-in text editor has an id of 'default' for all configurations. + */ + readonly viewId: string | undefined; + + /** + * All the resources and viewIds represented by a tab + * {@link Tab.resource resource} and {@link Tab.viewId viewId} will + * always be at index 0. + */ + readonly additionalResourcesAndViewIds: readonly { + readonly resource: Uri | undefined, + readonly viewId: string | undefined + }[]; + + /** + * Whether or not the tab is currently active + * Dictated by being the selected tab in the active group + */ + readonly isActive: boolean; + + /** + * Moves a tab to the given index within the column. + * If the index is out of range, the tab will be moved to the end of the column. + * If the column is out of range, a new one will be created after the last existing column. + * @param index The index to move the tab to + * @param viewColumn The column to move the tab into + */ + move(index: number, viewColumn: ViewColumn): Thenable; + + /** + * Closes the tab. This makes the tab object invalid and the tab + * should no longer be used for further actions. + */ + close(): Thenable; + } + + export namespace window { + /** + * A list of all opened tabs + * Ordered from left to right + */ + export const tabs: readonly Tab[]; + + /** + * The currently active tab + * Undefined if no tabs are currently opened + */ + export const activeTab: Tab | undefined; + + /** + * An {@link Event} which fires when the array of {@link window.tabs tabs} + * has changed. + */ + export const onDidChangeTabs: Event; + + /** + * An {@link Event} which fires when the {@link window.activeTab activeTab} + * has changed. + */ + export const onDidChangeActiveTab: Event; + + } +} diff --git a/src/vscode-dts/vscode.proposed.taskPresentationGroup.d.ts b/src/vscode-dts/vscode.proposed.taskPresentationGroup.d.ts new file mode 100644 index 00000000000..52e631a0495 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.taskPresentationGroup.d.ts @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/47265 + + export interface TaskPresentationOptions { + /** + * Controls whether the task is executed in a specific terminal group using split panes. + */ + group?: string; + + /** + * Controls whether the terminal is closed after executing the task. + */ + close?: boolean; + } +} diff --git a/src/vscode-dts/vscode.proposed.terminalDataWriteEvent.d.ts b/src/vscode-dts/vscode.proposed.terminalDataWriteEvent.d.ts new file mode 100644 index 00000000000..c9c4c0e99af --- /dev/null +++ b/src/vscode-dts/vscode.proposed.terminalDataWriteEvent.d.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/78502 + + export interface TerminalDataWriteEvent { + /** + * The {@link Terminal} for which the data was written. + */ + readonly terminal: Terminal; + /** + * The data being written. + */ + readonly data: string; + } + + namespace window { + /** + * An event which fires when the terminal's child pseudo-device is written to (the shell). + * In other words, this provides access to the raw data stream from the process running + * within the terminal, including VT sequences. + */ + export const onDidWriteTerminalData: Event; + } +} diff --git a/src/vscode-dts/vscode.proposed.terminalDimensions.d.ts b/src/vscode-dts/vscode.proposed.terminalDimensions.d.ts new file mode 100644 index 00000000000..a0d28379d60 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.terminalDimensions.d.ts @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/55718 + + /** + * An {@link Event} which fires when a {@link Terminal}'s dimensions change. + */ + export interface TerminalDimensionsChangeEvent { + /** + * The {@link Terminal} for which the dimensions have changed. + */ + readonly terminal: Terminal; + /** + * The new value for the {@link Terminal.dimensions terminal's dimensions}. + */ + readonly dimensions: TerminalDimensions; + } + + export namespace window { + /** + * An event which fires when the {@link Terminal.dimensions dimensions} of the terminal change. + */ + export const onDidChangeTerminalDimensions: Event; + } + + export interface Terminal { + /** + * The current dimensions of the terminal. This will be `undefined` immediately after the + * terminal is created as the dimensions are not known until shortly after the terminal is + * created. + */ + readonly dimensions: TerminalDimensions | undefined; + } +} diff --git a/src/vscode-dts/vscode.proposed.terminalLocation.d.ts b/src/vscode-dts/vscode.proposed.terminalLocation.d.ts new file mode 100644 index 00000000000..9c2a10a3feb --- /dev/null +++ b/src/vscode-dts/vscode.proposed.terminalLocation.d.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/45407 + + export interface TerminalOptions { + location?: TerminalLocation | TerminalEditorLocationOptions | TerminalSplitLocationOptions; + } + + export interface ExtensionTerminalOptions { + location?: TerminalLocation | TerminalEditorLocationOptions | TerminalSplitLocationOptions; + } + + export enum TerminalLocation { + Panel = 1, + Editor = 2, + } + + export interface TerminalEditorLocationOptions { + /** + * A view column in which the {@link Terminal terminal} should be shown in the editor area. + * Use {@link ViewColumn.Active active} to open in the active editor group, other values are + * adjusted to be `Min(column, columnCount + 1)`, the + * {@link ViewColumn.Active active}-column is not adjusted. Use + * {@linkcode ViewColumn.Beside} to open the editor to the side of the currently active one. + */ + viewColumn: ViewColumn; + /** + * An optional flag that when `true` will stop the {@link Terminal} from taking focus. + */ + preserveFocus?: boolean; + } + + export interface TerminalSplitLocationOptions { + /** + * The parent terminal to split this terminal beside. This works whether the parent terminal + * is in the panel or the editor area. + */ + parentTerminal: Terminal; + } +} diff --git a/src/vscode-dts/vscode.proposed.terminalNameChangeEvent.d.ts b/src/vscode-dts/vscode.proposed.terminalNameChangeEvent.d.ts new file mode 100644 index 00000000000..e72aeca9b82 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.terminalNameChangeEvent.d.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // todo@API,@jrieken is this needed? This is also in vscode.d.ts... + // https://github.com/microsoft/vscode/issues/114898 + + export interface Pseudoterminal { + /** + * An event that when fired allows changing the name of the terminal. + * + * **Example:** Change the terminal name to "My new terminal". + * ```typescript + * const writeEmitter = new vscode.EventEmitter(); + * const changeNameEmitter = new vscode.EventEmitter(); + * const pty: vscode.Pseudoterminal = { + * onDidWrite: writeEmitter.event, + * onDidChangeName: changeNameEmitter.event, + * open: () => changeNameEmitter.fire('My new terminal'), + * close: () => {} + * }; + * vscode.window.createTerminal({ name: 'My terminal', pty }); + * ``` + */ + onDidChangeName?: Event; + } +} diff --git a/src/vscode-dts/vscode.proposed.testCoverage.d.ts b/src/vscode-dts/vscode.proposed.testCoverage.d.ts new file mode 100644 index 00000000000..3f0ec8f1d67 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.testCoverage.d.ts @@ -0,0 +1,198 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/123713 + + export interface TestRun { + /** + * Test coverage provider for this result. An extension can defer setting + * this until after a run is complete and coverage is available. + */ + coverageProvider?: TestCoverageProvider + // ... + } + + /** + * Provides information about test coverage for a test result. + * Methods on the provider will not be called until the test run is complete + */ + export interface TestCoverageProvider { + /** + * Returns coverage information for all files involved in the test run. + * @param token A cancellation token. + * @return Coverage metadata for all files involved in the test. + */ + provideFileCoverage(token: CancellationToken): ProviderResult; + + /** + * Give a FileCoverage to fill in more data, namely {@link FileCoverage.detailedCoverage}. + * The editor will only resolve a FileCoverage once, and onyl if detailedCoverage + * is undefined. + * + * @param coverage A coverage object obtained from {@link provideFileCoverage} + * @param token A cancellation token. + * @return The resolved file coverage, or a thenable that resolves to one. It + * is OK to return the given `coverage`. When no result is returned, the + * given `coverage` will be used. + */ + resolveFileCoverage?(coverage: T, token: CancellationToken): ProviderResult; + } + + /** + * A class that contains information about a covered resource. A count can + * be give for lines, branches, and functions in a file. + */ + export class CoveredCount { + /** + * Number of items covered in the file. + */ + covered: number; + /** + * Total number of covered items in the file. + */ + total: number; + + /** + * @param covered Value for {@link CovereredCount.covered} + * @param total Value for {@link CovereredCount.total} + */ + constructor(covered: number, total: number); + } + + /** + * Contains coverage metadata for a file. + */ + export class FileCoverage { + /** + * File URI. + */ + readonly uri: Uri; + + /** + * Statement coverage information. If the reporter does not provide statement + * coverage information, this can instead be used to represent line coverage. + */ + statementCoverage: CoveredCount; + + /** + * Branch coverage information. + */ + branchCoverage?: CoveredCount; + + /** + * Function coverage information. + */ + functionCoverage?: CoveredCount; + + /** + * Detailed, per-statement coverage. If this is undefined, the editor will + * call {@link TestCoverageProvider.resolveFileCoverage} when necessary. + */ + detailedCoverage?: DetailedCoverage[]; + + /** + * Creates a {@link FileCoverage} instance with counts filled in from + * the coverage details. + * @param uri Covered file URI + * @param detailed Detailed coverage information + */ + static fromDetails(uri: Uri, details: readonly DetailedCoverage[]): FileCoverage; + + /** + * @param uri Covered file URI + * @param statementCoverage Statement coverage information. If the reporter + * does not provide statement coverage information, this can instead be + * used to represent line coverage. + * @param branchCoverage Branch coverage information + * @param functionCoverage Function coverage information + */ + constructor( + uri: Uri, + statementCoverage: CoveredCount, + branchCoverage?: CoveredCount, + functionCoverage?: CoveredCount, + ); + } + + /** + * Contains coverage information for a single statement or line. + */ + export class StatementCoverage { + /** + * The number of times this statement was executed. If zero, the + * statement will be marked as un-covered. + */ + executionCount: number; + + /** + * Statement location. + */ + location: Position | Range; + + /** + * Coverage from branches of this line or statement. If it's not a + * conditional, this will be empty. + */ + branches: BranchCoverage[]; + + /** + * @param location The statement position. + * @param executionCount The number of times this statement was + * executed. If zero, the statement will be marked as un-covered. + * @param branches Coverage from branches of this line. If it's not a + * conditional, this should be omitted. + */ + constructor(executionCount: number, location: Position | Range, branches?: BranchCoverage[]); + } + + /** + * Contains coverage information for a branch of a {@link StatementCoverage}. + */ + export class BranchCoverage { + /** + * The number of times this branch was executed. If zero, the + * branch will be marked as un-covered. + */ + executionCount: number; + + /** + * Branch location. + */ + location?: Position | Range; + + /** + * @param executionCount The number of times this branch was executed. + * @param location The branch position. + */ + constructor(executionCount: number, location?: Position | Range); + } + + /** + * Contains coverage information for a function or method. + */ + export class FunctionCoverage { + /** + * The number of times this function was executed. If zero, the + * function will be marked as un-covered. + */ + executionCount: number; + + /** + * Function location. + */ + location: Position | Range; + + /** + * @param executionCount The number of times this function was executed. + * @param location The function position. + */ + constructor(executionCount: number, location: Position | Range); + } + + export type DetailedCoverage = StatementCoverage | FunctionCoverage; + +} diff --git a/src/vscode-dts/vscode.proposed.testObserver.d.ts b/src/vscode-dts/vscode.proposed.testObserver.d.ts new file mode 100644 index 00000000000..2bdb21d7473 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.testObserver.d.ts @@ -0,0 +1,202 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/107467 + + export namespace tests { + /** + * Requests that tests be run by their controller. + * @param run Run options to use. + * @param token Cancellation token for the test run + */ + export function runTests(run: TestRunRequest, token?: CancellationToken): Thenable; + + /** + * Returns an observer that watches and can request tests. + */ + export function createTestObserver(): TestObserver; + /** + * List of test results stored by the editor, sorted in descending + * order by their `completedAt` time. + */ + export const testResults: ReadonlyArray; + + /** + * Event that fires when the {@link testResults} array is updated. + */ + export const onDidChangeTestResults: Event; + } + + export interface TestObserver { + /** + * List of tests returned by test provider for files in the workspace. + */ + readonly tests: ReadonlyArray; + + /** + * An event that fires when an existing test in the collection changes, or + * null if a top-level test was added or removed. When fired, the consumer + * should check the test item and all its children for changes. + */ + readonly onDidChangeTest: Event; + + /** + * Dispose of the observer, allowing the editor to eventually tell test + * providers that they no longer need to update tests. + */ + dispose(): void; + } + + export interface TestsChangeEvent { + /** + * List of all tests that are newly added. + */ + readonly added: ReadonlyArray; + + /** + * List of existing tests that have updated. + */ + readonly updated: ReadonlyArray; + + /** + * List of existing tests that have been removed. + */ + readonly removed: ReadonlyArray; + } + + /** + * A test item is an item shown in the "test explorer" view. It encompasses + * both a suite and a test, since they have almost or identical capabilities. + */ + export interface TestItem { + /** + * Marks the test as outdated. This can happen as a result of file changes, + * for example. In "auto run" mode, tests that are outdated will be + * automatically rerun after a short delay. Invoking this on a + * test with children will mark the entire subtree as outdated. + * + * Extensions should generally not override this method. + */ + // todo@api still unsure about this + invalidateResults(): void; + } + + + /** + * TestResults can be provided to the editor in {@link tests.publishTestResult}, + * or read from it in {@link tests.testResults}. + * + * The results contain a 'snapshot' of the tests at the point when the test + * run is complete. Therefore, information such as its {@link Range} may be + * out of date. If the test still exists in the workspace, consumers can use + * its `id` to correlate the result instance with the living test. + */ + export interface TestRunResult { + /** + * Unix milliseconds timestamp at which the test run was completed. + */ + readonly completedAt: number; + + /** + * Optional raw output from the test run. + */ + readonly output?: string; + + /** + * List of test results. The items in this array are the items that + * were passed in the {@link tests.runTests} method. + */ + readonly results: ReadonlyArray>; + } + + /** + * A {@link TestItem}-like interface with an associated result, which appear + * or can be provided in {@link TestResult} interfaces. + */ + export interface TestResultSnapshot { + /** + * Unique identifier that matches that of the associated TestItem. + * This is used to correlate test results and tests in the document with + * those in the workspace (test explorer). + */ + readonly id: string; + + /** + * Parent of this item. + */ + readonly parent?: TestResultSnapshot; + + /** + * URI this TestItem is associated with. May be a file or file. + */ + readonly uri?: Uri; + + /** + * Display name describing the test case. + */ + readonly label: string; + + /** + * Optional description that appears next to the label. + */ + readonly description?: string; + + /** + * Location of the test item in its `uri`. This is only meaningful if the + * `uri` points to a file. + */ + readonly range?: Range; + + /** + * State of the test in each task. In the common case, a test will only + * be executed in a single task and the length of this array will be 1. + */ + readonly taskStates: ReadonlyArray; + + /** + * Optional list of nested tests for this item. + */ + readonly children: Readonly[]; + } + + export interface TestSnapshotTaskState { + /** + * Current result of the test. + */ + readonly state: TestResultState; + + /** + * The number of milliseconds the test took to run. This is set once the + * `state` is `Passed`, `Failed`, or `Errored`. + */ + readonly duration?: number; + + /** + * Associated test run message. Can, for example, contain assertion + * failure information if the test fails. + */ + readonly messages: ReadonlyArray; + } + + /** + * Possible states of tests in a test run. + */ + export enum TestResultState { + // Test will be run, but is not currently running. + Queued = 1, + // Test is currently running + Running = 2, + // Test run has passed + Passed = 3, + // Test run has failed (on an assertion) + Failed = 4, + // Test run has been skipped + Skipped = 5, + // Test run failed for some other reason (compilation error, timeout, etc) + Errored = 6 + } +} diff --git a/src/vscode-dts/vscode.proposed.textDocumentNotebook.d.ts b/src/vscode-dts/vscode.proposed.textDocumentNotebook.d.ts new file mode 100644 index 00000000000..0b7e1f7b391 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.textDocumentNotebook.d.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/102091 + + export interface TextDocument { + + /** + * The {@link NotebookDocument notebook} that contains this document as a notebook cell or `undefined` when + * the document is not contained by a notebook (this should be the more frequent case). + */ + notebook: NotebookDocument | undefined; + } +} diff --git a/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts b/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts new file mode 100644 index 00000000000..9eba555d007 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts @@ -0,0 +1,275 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/59921 + + /** + * The parameters of a query for text search. + */ + export interface TextSearchQuery { + /** + * The text pattern to search for. + */ + pattern: string; + + /** + * Whether or not `pattern` should match multiple lines of text. + */ + isMultiline?: boolean; + + /** + * Whether or not `pattern` should be interpreted as a regular expression. + */ + isRegExp?: boolean; + + /** + * Whether or not the search should be case-sensitive. + */ + isCaseSensitive?: boolean; + + /** + * Whether or not to search for whole word matches only. + */ + isWordMatch?: boolean; + } + + /** + * A file glob pattern to match file paths against. + * TODO@roblourens merge this with the GlobPattern docs/definition in vscode.d.ts. + * @see {@link GlobPattern} + */ + export type GlobString = string; + + /** + * Options common to file and text search + */ + export interface SearchOptions { + /** + * The root folder to search within. + */ + folder: Uri; + + /** + * Files that match an `includes` glob pattern should be included in the search. + */ + includes: GlobString[]; + + /** + * Files that match an `excludes` glob pattern should be excluded from the search. + */ + excludes: GlobString[]; + + /** + * Whether external files that exclude files, like .gitignore, should be respected. + * See the vscode setting `"search.useIgnoreFiles"`. + */ + useIgnoreFiles: boolean; + + /** + * Whether symlinks should be followed while searching. + * See the vscode setting `"search.followSymlinks"`. + */ + followSymlinks: boolean; + + /** + * Whether global files that exclude files, like .gitignore, should be respected. + * See the vscode setting `"search.useGlobalIgnoreFiles"`. + */ + useGlobalIgnoreFiles: boolean; + } + + /** + * Options to specify the size of the result text preview. + * These options don't affect the size of the match itself, just the amount of preview text. + */ + export interface TextSearchPreviewOptions { + /** + * The maximum number of lines in the preview. + * Only search providers that support multiline search will ever return more than one line in the match. + */ + matchLines: number; + + /** + * The maximum number of characters included per line. + */ + charsPerLine: number; + } + + /** + * Options that apply to text search. + */ + export interface TextSearchOptions extends SearchOptions { + /** + * The maximum number of results to be returned. + */ + maxResults: number; + + /** + * Options to specify the size of the result text preview. + */ + previewOptions?: TextSearchPreviewOptions; + + /** + * Exclude files larger than `maxFileSize` in bytes. + */ + maxFileSize?: number; + + /** + * Interpret files using this encoding. + * See the vscode setting `"files.encoding"` + */ + encoding?: string; + + /** + * Number of lines of context to include before each match. + */ + beforeContext?: number; + + /** + * Number of lines of context to include after each match. + */ + afterContext?: number; + } + + /** + * Represents the severiry of a TextSearchComplete message. + */ + export enum TextSearchCompleteMessageType { + Information = 1, + Warning = 2, + } + + /** + * A message regarding a completed search. + */ + export interface TextSearchCompleteMessage { + /** + * Markdown text of the message. + */ + text: string, + /** + * Whether the source of the message is trusted, command links are disabled for untrusted message sources. + * Messaged are untrusted by default. + */ + trusted?: boolean, + /** + * The message type, this affects how the message will be rendered. + */ + type: TextSearchCompleteMessageType, + } + + /** + * Information collected when text search is complete. + */ + export interface TextSearchComplete { + /** + * Whether the search hit the limit on the maximum number of search results. + * `maxResults` on {@linkcode TextSearchOptions} specifies the max number of results. + * - If exactly that number of matches exist, this should be false. + * - If `maxResults` matches are returned and more exist, this should be true. + * - If search hits an internal limit which is less than `maxResults`, this should be true. + */ + limitHit?: boolean; + + /** + * Additional information regarding the state of the completed search. + * + * Messages with "Information" style support links in markdown syntax: + * - Click to [run a command](command:workbench.action.OpenQuickPick) + * - Click to [open a website](https://aka.ms) + * + * Commands may optionally return { triggerSearch: true } to signal to the editor that the original search should run be again. + */ + message?: TextSearchCompleteMessage | TextSearchCompleteMessage[]; + } + + /** + * A preview of the text result. + */ + export interface TextSearchMatchPreview { + /** + * The matching lines of text, or a portion of the matching line that contains the match. + */ + text: string; + + /** + * The Range within `text` corresponding to the text of the match. + * The number of matches must match the TextSearchMatch's range property. + */ + matches: Range | Range[]; + } + + /** + * A match from a text search + */ + export interface TextSearchMatch { + /** + * The uri for the matching document. + */ + uri: Uri; + + /** + * The range of the match within the document, or multiple ranges for multiple matches. + */ + ranges: Range | Range[]; + + /** + * A preview of the text match. + */ + preview: TextSearchMatchPreview; + } + + /** + * A line of context surrounding a TextSearchMatch. + */ + export interface TextSearchContext { + /** + * The uri for the matching document. + */ + uri: Uri; + + /** + * One line of text. + * previewOptions.charsPerLine applies to this + */ + text: string; + + /** + * The line number of this line of context. + */ + lineNumber: number; + } + + export type TextSearchResult = TextSearchMatch | TextSearchContext; + + /** + * A TextSearchProvider provides search results for text results inside files in the workspace. + */ + export interface TextSearchProvider { + /** + * Provide results that match the given text pattern. + * @param query The parameters for this query. + * @param options A set of options to consider while searching. + * @param progress A progress callback that must be invoked for all results. + * @param token A cancellation token. + */ + provideTextSearchResults(query: TextSearchQuery, options: TextSearchOptions, progress: Progress, token: CancellationToken): ProviderResult; + } + + export namespace workspace { + /** + * Register a text search provider. + * + * Only one provider can be registered per scheme. + * + * @param scheme The provider will be invoked for workspace folders that have this file scheme. + * @param provider The provider. + * @return A {@link Disposable} that unregisters this provider when being disposed. + */ + export function registerTextSearchProvider(scheme: string, provider: TextSearchProvider): Disposable; + } +} diff --git a/src/vscode-dts/vscode.proposed.timeline.d.ts b/src/vscode-dts/vscode.proposed.timeline.d.ts new file mode 100644 index 00000000000..625bc8c9bed --- /dev/null +++ b/src/vscode-dts/vscode.proposed.timeline.d.ts @@ -0,0 +1,163 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/84297 + + export class TimelineItem { + /** + * A timestamp (in milliseconds since 1 January 1970 00:00:00) for when the timeline item occurred. + */ + timestamp: number; + + /** + * A human-readable string describing the timeline item. + */ + label: string; + + /** + * Optional id for the timeline item. It must be unique across all the timeline items provided by this source. + * + * If not provided, an id is generated using the timeline item's timestamp. + */ + id?: string; + + /** + * The icon path or {@link ThemeIcon} for the timeline item. + */ + iconPath?: Uri | { light: Uri; dark: Uri; } | ThemeIcon; + + /** + * A human readable string describing less prominent details of the timeline item. + */ + description?: string; + + /** + * The tooltip text when you hover over the timeline item. + */ + detail?: string; + + /** + * The {@link Command} that should be executed when the timeline item is selected. + */ + command?: Command; + + /** + * Context value of the timeline item. This can be used to contribute specific actions to the item. + * For example, a timeline item is given a context value as `commit`. When contributing actions to `timeline/item/context` + * using `menus` extension point, you can specify context value for key `timelineItem` in `when` expression like `timelineItem == commit`. + * ``` + * "contributes": { + * "menus": { + * "timeline/item/context": [ + * { + * "command": "extension.copyCommitId", + * "when": "timelineItem == commit" + * } + * ] + * } + * } + * ``` + * This will show the `extension.copyCommitId` action only for items where `contextValue` is `commit`. + */ + contextValue?: string; + + /** + * Accessibility information used when screen reader interacts with this timeline item. + */ + accessibilityInformation?: AccessibilityInformation; + + /** + * @param label A human-readable string describing the timeline item + * @param timestamp A timestamp (in milliseconds since 1 January 1970 00:00:00) for when the timeline item occurred + */ + constructor(label: string, timestamp: number); + } + + export interface TimelineChangeEvent { + /** + * The {@link Uri} of the resource for which the timeline changed. + */ + uri: Uri; + + /** + * A flag which indicates whether the entire timeline should be reset. + */ + reset?: boolean; + } + + export interface Timeline { + readonly paging?: { + /** + * A provider-defined cursor specifying the starting point of timeline items which are after the ones returned. + * Use `undefined` to signal that there are no more items to be returned. + */ + readonly cursor: string | undefined; + }; + + /** + * An array of {@link TimelineItem timeline items}. + */ + readonly items: readonly TimelineItem[]; + } + + export interface TimelineOptions { + /** + * A provider-defined cursor specifying the starting point of the timeline items that should be returned. + */ + cursor?: string; + + /** + * An optional maximum number timeline items or the all timeline items newer (inclusive) than the timestamp or id that should be returned. + * If `undefined` all timeline items should be returned. + */ + limit?: number | { timestamp: number; id?: string; }; + } + + export interface TimelineProvider { + /** + * An optional event to signal that the timeline for a source has changed. + * To signal that the timeline for all resources (uris) has changed, do not pass any argument or pass `undefined`. + */ + onDidChange?: Event; + + /** + * An identifier of the source of the timeline items. This can be used to filter sources. + */ + readonly id: string; + + /** + * A human-readable string describing the source of the timeline items. This can be used as the display label when filtering sources. + */ + readonly label: string; + + /** + * Provide {@link TimelineItem timeline items} for a {@link Uri}. + * + * @param uri The {@link Uri} of the file to provide the timeline for. + * @param options A set of options to determine how results should be returned. + * @param token A cancellation token. + * @return The {@link TimelineResult timeline result} or a thenable that resolves to such. The lack of a result + * can be signaled by returning `undefined`, `null`, or an empty array. + */ + provideTimeline(uri: Uri, options: TimelineOptions, token: CancellationToken): ProviderResult; + } + + export namespace workspace { + /** + * Register a timeline provider. + * + * Multiple providers can be registered. In that case, providers are asked in + * parallel and the results are merged. A failing provider (rejected promise or exception) will + * not cause a failure of the whole operation. + * + * @param scheme A scheme or schemes that defines which documents this provider is applicable to. Can be `*` to target all documents. + * @param provider A timeline provider. + * @return A {@link Disposable} that unregisters this provider when being disposed. + */ + export function registerTimelineProvider(scheme: string | string[], provider: TimelineProvider): Disposable; + } +} diff --git a/src/vscode-dts/vscode.proposed.tokenInformation.d.ts b/src/vscode-dts/vscode.proposed.tokenInformation.d.ts new file mode 100644 index 00000000000..538788818d1 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.tokenInformation.d.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/91555 + + export enum StandardTokenType { + Other = 0, + Comment = 1, + String = 2, + RegEx = 4 + } + + export interface TokenInformation { + type: StandardTokenType; + range: Range; + } + + export namespace languages { + /** @deprecated */ + export function getTokenInformationAtPosition(document: TextDocument, position: Position): Thenable; + } +} diff --git a/src/vscode-dts/vscode.proposed.treeViewDragAndDrop.d.ts b/src/vscode-dts/vscode.proposed.treeViewDragAndDrop.d.ts new file mode 100644 index 00000000000..9db19f3260c --- /dev/null +++ b/src/vscode-dts/vscode.proposed.treeViewDragAndDrop.d.ts @@ -0,0 +1,111 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/32592 + + /** + * A data provider that provides tree data + */ + export interface TreeDataProvider { + /** + * An optional event to signal that an element or root has changed. + * This will trigger the view to update the changed element/root and its children recursively (if shown). + * To signal that root has changed, do not pass any argument or pass `undefined` or `null`. + */ + onDidChangeTreeData2?: Event; + } + + export interface TreeViewOptions { + /** + * An optional interface to implement drag and drop in the tree view. + */ + dragAndDropController?: DragAndDropController; + } + + /** + * A class for encapsulating data transferred during a tree drag and drop event. + * + * If your `DragAndDropController` implements `onWillDrop`, you can extend `TreeDataTransferItem` and return + * an instance of your new class for easy access to the source tree items. + * + * ```ts + * class TestViewObjectTransferItem extends vscode.TreeDataTransferItem { + * constructor(private _nodes: Node[]) { + * super(_nodes); + * } + * + * asObject(): Node[] { + * return this._nodes; + * } + * } + * ``` + */ + export class TreeDataTransferItem { + asString(): Thenable; + + constructor(value: any); + } + + /** + * A map containing a mapping of the mime type of the corresponding transferred data. + * Trees that support drag and drop can implement `DragAndDropController.onWillDrop` to add additional mime types + * when the drop occurs on an item in the same tree. + */ + export class TreeDataTransfer { + /** + * Retrieves the data transfer item for a given mime type. + * @param mimeType The mime type to get the data transfer item for. + */ + get(mimeType: string): T | undefined; + + /** + * Sets a mime type to data transfer item mapping. + * @param mimeType The mime type to set the data for. + * @param value The data transfer item for the given mime type. + */ + set(mimeType: string, value: T): void; + + /** + * Allows iteration through the data transfer items. + * @param callbackfn Callback for iteration through the data transfer items. + */ + forEach(callbackfn: (value: T, key: string) => void): void; + } + + /** + * Provides support for drag and drop in `TreeView`. + */ + export interface DragAndDropController extends Disposable { + + /** + * The mime types that this `DragAndDropController` supports. This could be well-defined, existing, mime types, + * and also mime types defined by the extension that are returned in the `TreeDataTransfer` from `onWillDrop`. + */ + readonly supportedMimeTypes: string[]; + + /** + * When the user drops an item from this DragAndDropController on **another tree item** in **the same tree**, + * `onWillDrop` will be called with the dropped tree items. This is the DragAndDropController's opportunity to + * package the data from the dropped tree item into whatever format they want the target tree item to receive. + * + * The returned `TreeDataTransfer` will be merged with the original`TreeDataTransfer` for the operation. + * + * @param source The source items for the drag and drop operation. + */ + onWillDrop?(source: T[]): Thenable; + + /** + * Called when a drag and drop action results in a drop on the tree that this `DragAndDropController` belongs too. + * + * Extensions should fire `TreeDataProvider.onDidChangeTreeData` for any elements that need to be refreshed. + * + * @param source The data transfer items of the source of the drag. + * @param target The target tree element that the drop is occuring on. + */ + onDrop(source: TreeDataTransfer, target: T): Thenable; + } +} diff --git a/src/vscode-dts/vscode.proposed.treeViewReveal.d.ts b/src/vscode-dts/vscode.proposed.treeViewReveal.d.ts new file mode 100644 index 00000000000..e71c2f71879 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.treeViewReveal.d.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/61313 @alexr00 + + export interface TreeView extends Disposable { + reveal(element: T | undefined, options?: { select?: boolean, focus?: boolean, expand?: boolean | number; }): Thenable; + } +} diff --git a/src/vscode-dts/vscode.proposed.workspaceTrust.d.ts b/src/vscode-dts/vscode.proposed.workspaceTrust.d.ts new file mode 100644 index 00000000000..d48071af2cc --- /dev/null +++ b/src/vscode-dts/vscode.proposed.workspaceTrust.d.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/120173 + + /** + * The object describing the properties of the workspace trust request + */ + export interface WorkspaceTrustRequestOptions { + /** + * Custom message describing the user action that requires workspace + * trust. If omitted, a generic message will be displayed in the workspace + * trust request dialog. + */ + readonly message?: string; + } + + export namespace workspace { + /** + * Prompt the user to chose whether to trust the current workspace + * @param options Optional object describing the properties of the + * workspace trust request. + */ + export function requestWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Thenable; + } +} diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index 6f7a8a7e9ee..8deb024b4ae 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -225,14 +225,13 @@ async function poll( if (trial > retryCount) { console.error('** Timeout!'); console.error(lastError); - + console.error(`Timeout: ${timeoutMessage} after ${(retryCount * retryInterval) / 1000} seconds.`); throw new Error(`Timeout: ${timeoutMessage} after ${(retryCount * retryInterval) / 1000} seconds.`); } let result; try { result = await fn(); - if (acceptFn(result)) { return result; } else { @@ -250,7 +249,7 @@ async function poll( export class Code { private _activeWindowId: number | undefined = undefined; - private driver: IDriver; + driver: IDriver; constructor( private client: IDisposable, diff --git a/test/automation/src/playwrightDriver.ts b/test/automation/src/playwrightDriver.ts index baa15ef0aef..9fd2c83b8e7 100644 --- a/test/automation/src/playwrightDriver.ts +++ b/test/automation/src/playwrightDriver.ts @@ -8,9 +8,10 @@ import { ChildProcess, spawn } from 'child_process'; import { join } from 'path'; import { mkdir } from 'fs'; import { promisify } from 'util'; -import { IDriver, IDisposable } from './driver'; +import { IDriver, IDisposable, IWindowDriver } from './driver'; import { URI } from 'vscode-uri'; import * as kill from 'tree-kill'; +import { PageFunction } from 'playwright-core/types/structs'; const width = 1200; const height = 800; @@ -35,69 +36,113 @@ const vscodeToPlaywrightKey: { [key: string]: string } = { let traceCounter = 1; function buildDriver(browser: playwright.Browser, context: playwright.BrowserContext, page: playwright.Page): IDriver { - const driver: IDriver = { - _serviceBrand: undefined, - getWindowIds: () => { - return Promise.resolve([1]); - }, - capturePage: () => Promise.resolve(''), - reloadWindow: (windowId) => Promise.resolve(), - exitApplication: async () => { - try { - await context.tracing.stop({ path: join(logsPath, `playwright-trace-${traceCounter++}.zip`) }); - } catch (error) { - console.warn(`Failed to stop playwright tracing.`); // do not fail the build when this fails - } - await browser.close(); - await teardown(); + return new PlaywrightDriver(browser, context, page); +} - return false; - }, - dispatchKeybinding: async (windowId, keybinding) => { - const chords = keybinding.split(' '); - for (let i = 0; i < chords.length; i++) { - const chord = chords[i]; - if (i > 0) { - await timeout(100); - } - const keys = chord.split('+'); - const keysDown: string[] = []; - for (let i = 0; i < keys.length; i++) { - if (keys[i] in vscodeToPlaywrightKey) { - keys[i] = vscodeToPlaywrightKey[keys[i]]; - } - await page.keyboard.down(keys[i]); - keysDown.push(keys[i]); - } - while (keysDown.length > 0) { - await page.keyboard.up(keysDown.pop()!); - } +class PlaywrightDriver implements IDriver { + _serviceBrand: undefined; + page: playwright.Page; + constructor( + private readonly _browser: playwright.Browser, + private readonly _context: playwright.BrowserContext, + private readonly _page: playwright.Page + ) { + this.page = _page; + } + + async getWindowIds() { return [1]; } + async capturePage() { return ''; } + async reloadWindow(windowId: number) { } + async exitApplication() { + try { + await this._context.tracing.stop({ path: join(logsPath, `playwright-trace-${traceCounter++}.zip`) }); + } catch (error) { + console.warn(`Failed to stop playwright tracing.`); // do not fail the build when this fails + } + await this._browser.close(); + await teardown(); + + return false; + } + async dispatchKeybinding(windowId: number, keybinding: string) { + const chords = keybinding.split(' '); + for (let i = 0; i < chords.length; i++) { + const chord = chords[i]; + if (i > 0) { + await timeout(100); } - await timeout(100); - }, - click: async (windowId, selector, xoffset, yoffset) => { - const { x, y } = await driver.getElementXY(windowId, selector, xoffset, yoffset); - await page.mouse.click(x + (xoffset ? xoffset : 0), y + (yoffset ? yoffset : 0)); - }, - doubleClick: async (windowId, selector) => { - await driver.click(windowId, selector, 0, 0); - await timeout(60); - await driver.click(windowId, selector, 0, 0); - await timeout(100); - }, - setValue: async (windowId, selector, text) => page.evaluate(`window.driver.setValue('${selector}', '${text}')`).then(undefined), - getTitle: (windowId) => page.evaluate(`window.driver.getTitle()`), - isActiveElement: (windowId, selector) => page.evaluate(`window.driver.isActiveElement('${selector}')`), - getElements: (windowId, selector, recursive) => page.evaluate(`window.driver.getElements('${selector}', ${recursive})`), - getElementXY: (windowId, selector, xoffset?, yoffset?) => page.evaluate(`window.driver.getElementXY('${selector}', ${xoffset}, ${yoffset})`), - typeInEditor: (windowId, selector, text) => page.evaluate(`window.driver.typeInEditor('${selector}', '${text}')`), - getTerminalBuffer: (windowId, selector) => page.evaluate(`window.driver.getTerminalBuffer('${selector}')`), - writeInTerminal: (windowId, selector, text) => page.evaluate(`window.driver.writeInTerminal('${selector}', '${text}')`), - getLocaleInfo: (windowId) => page.evaluate(`window.driver.getLocaleInfo()`), - getLocalizedStrings: (windowId) => page.evaluate(`window.driver.getLocalizedStrings()`) - }; - return driver; + if (keybinding.startsWith('Alt') || keybinding.startsWith('Control') || keybinding.startsWith('Backspace')) { + await this._page.keyboard.press(keybinding); + return; + } + + const keys = chord.split('+'); + const keysDown: string[] = []; + for (let i = 0; i < keys.length; i++) { + if (keys[i] in vscodeToPlaywrightKey) { + keys[i] = vscodeToPlaywrightKey[keys[i]]; + } + await this._page.keyboard.down(keys[i]); + keysDown.push(keys[i]); + } + while (keysDown.length > 0) { + await this._page.keyboard.up(keysDown.pop()!); + } + } + + await timeout(100); + } + async click(windowId: number, selector: string, xoffset?: number | undefined, yoffset?: number | undefined) { + const { x, y } = await this.getElementXY(windowId, selector, xoffset, yoffset); + await this._page.mouse.click(x + (xoffset ? xoffset : 0), y + (yoffset ? yoffset : 0)); + } + async doubleClick(windowId: number, selector: string) { + await this.click(windowId, selector, 0, 0); + await timeout(60); + await this.click(windowId, selector, 0, 0); + await timeout(100); + } + + async setValue(windowId: number, selector: string, text: string) { + return this._page.evaluate(([driver, selector, text]) => driver.setValue(selector, text), [await this._getDriverHandle(), selector, text] as const); + } + async getTitle(windowId: number) { + return this._evaluateWithDriver(([driver]) => driver.getTitle()); + } + async isActiveElement(windowId: number, selector: string) { + return this._page.evaluate(([driver, selector]) => driver.isActiveElement(selector), [await this._getDriverHandle(), selector] as const); + } + async getElements(windowId: number, selector: string, recursive: boolean = false) { + return this._page.evaluate(([driver, selector, recursive]) => driver.getElements(selector, recursive), [await this._getDriverHandle(), selector, recursive] as const); + } + async getElementXY(windowId: number, selector: string, xoffset?: number, yoffset?: number) { + return this._page.evaluate(([driver, selector, xoffset, yoffset]) => driver.getElementXY(selector, xoffset, yoffset), [await this._getDriverHandle(), selector, xoffset, yoffset] as const); + } + async typeInEditor(windowId: number, selector: string, text: string) { + return this._page.evaluate(([driver, selector, text]) => driver.typeInEditor(selector, text), [await this._getDriverHandle(), selector, text] as const); + } + async getTerminalBuffer(windowId: number, selector: string) { + return this._page.evaluate(([driver, selector]) => driver.getTerminalBuffer(selector), [await this._getDriverHandle(), selector] as const); + } + async writeInTerminal(windowId: number, selector: string, text: string) { + return this._page.evaluate(([driver, selector, text]) => driver.writeInTerminal(selector, text), [await this._getDriverHandle(), selector, text] as const); + } + async getLocaleInfo(windowId: number) { + return this._evaluateWithDriver(([driver]) => driver.getLocaleInfo()); + } + async getLocalizedStrings(windowId: number) { + return this._evaluateWithDriver(([driver]) => driver.getLocalizedStrings()); + } + + private async _evaluateWithDriver(pageFunction: PageFunction[], T>) { + return this._page.evaluate(pageFunction, [await this._getDriverHandle()]); + } + + // TODO: Cache + private async _getDriverHandle(): Promise> { + return this._page.evaluateHandle('window.driver'); + } } function timeout(ms: number): Promise { diff --git a/test/automation/src/quickaccess.ts b/test/automation/src/quickaccess.ts index 918d213050b..86438768f37 100644 --- a/test/automation/src/quickaccess.ts +++ b/test/automation/src/quickaccess.ts @@ -40,22 +40,53 @@ export class QuickAccess { } async openFile(fileName: string): Promise { - await this.openQuickAccess(fileName); + let retries = 0; + let fileFound = false; + while (++retries < 10) { + let retry = false; + + await this.openQuickAccess(fileName); + + await this.quickInput.waitForQuickInputElements(names => { + const name = names[0]; + if (name === fileName) { + fileFound = true; + return true; + } + + if (name === 'No matching results') { + retry = true; + return true; + } + + return false; + }); + + if (!retry) { + break; + } + + await this.quickInput.closeQuickInput(); + await new Promise(c => setTimeout(c, 500)); + } + + if (!fileFound) { + throw new Error(`Quick open file search was unable to find '${fileName}' after 10 attempts, giving up.`); + } - await this.quickInput.waitForQuickInputElements(names => names[0] === fileName); await this.code.dispatchKeybinding('enter'); await this.editors.waitForActiveTab(fileName); await this.editors.waitForEditorFocus(fileName); } - async runCommand(commandId: string): Promise { + async runCommand(commandId: string, keepOpen?: boolean): Promise { await this.openQuickAccess(`>${commandId}`); // wait for best choice to be focused await this.code.waitForTextContent(QuickInput.QUICK_INPUT_FOCUSED_ELEMENT); // wait and click on best choice - await this.quickInput.selectQuickInputElement(0); + await this.quickInput.selectQuickInputElement(0, keepOpen); } async openQuickOutline(): Promise { @@ -75,7 +106,7 @@ export class QuickAccess { } await this.quickInput.closeQuickInput(); - await new Promise(c => setTimeout(c, 250)); + await new Promise(c => setTimeout(c, 500)); } } } diff --git a/test/automation/src/quickinput.ts b/test/automation/src/quickinput.ts index 70b37ea9e2d..78170370fd9 100644 --- a/test/automation/src/quickinput.ts +++ b/test/automation/src/quickinput.ts @@ -39,12 +39,14 @@ export class QuickInput { await this.code.waitForElement(QuickInput.QUICK_INPUT, r => !!r && r.attributes.style.indexOf('display: none;') !== -1); } - async selectQuickInputElement(index: number): Promise { + async selectQuickInputElement(index: number, keepOpen?: boolean): Promise { await this.waitForQuickInputOpened(); for (let from = 0; from < index; from++) { await this.code.dispatchKeybinding('down'); } await this.code.dispatchKeybinding('enter'); - await this.waitForQuickInputClosed(); + if (!keepOpen) { + await this.waitForQuickInputClosed(); + } } } diff --git a/test/automation/src/terminal.ts b/test/automation/src/terminal.ts index 207f8c90b16..0108ec4d759 100644 --- a/test/automation/src/terminal.ts +++ b/test/automation/src/terminal.ts @@ -3,31 +3,171 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { QuickInput } from '.'; import { Code } from './code'; import { QuickAccess } from './quickaccess'; -const PANEL_SELECTOR = 'div[id="workbench.panel.terminal"]'; -const XTERM_SELECTOR = `${PANEL_SELECTOR} .terminal-wrapper`; -const XTERM_TEXTAREA = `${XTERM_SELECTOR} textarea.xterm-helper-textarea`; +export enum Selector { + TerminalView = `#terminal`, + Xterm = `#terminal .terminal-wrapper`, + TabsEntry = '.terminal-tabs-entry', + XtermFocused = '.terminal.xterm.focus', + PlusButton = '.codicon-plus', + EditorGroups = '.editor .split-view-view', + EditorTab = '.terminal-tab', + EditorTabIcon = '.terminal-tab.codicon-', + SingleTab = '.single-terminal-tab', + Tabs = '.tabs-list .monaco-list-row', + SplitButton = '.editor .codicon-split-horizontal' +} + +export enum TerminalCommandIdWithValue { + Rename = 'workbench.action.terminal.rename', + ChangeColor = 'workbench.action.terminal.changeColor', + ChangeIcon = 'workbench.action.terminal.changeIcon', + NewWithProfile = 'workbench.action.terminal.newWithProfile', + SelectDefaultProfile = 'workbench.action.terminal.selectDefaultShell' +} + +export enum TerminalCommandId { + Split = 'workbench.action.terminal.split', + KillAll = 'workbench.action.terminal.killAll', + Unsplit = 'workbench.action.terminal.unsplit', + Join = 'workbench.action.terminal.join', + Show = 'workbench.action.terminal.toggleTerminal', + CreateNew = 'workbench.action.terminal.new', + CreateNewEditor = 'workbench.action.createTerminalEditor', + SplitEditor = 'workbench.action.createTerminalEditorSide', + MoveToPanel = 'workbench.action.terminal.moveToTerminalPanel', + MoveToEditor = 'workbench.action.terminal.moveToEditor', + NewWithProfile = 'workbench.action.terminal.newWithProfile', + SelectDefaultProfile = 'workbench.action.terminal.selectDefaultShell' +} +interface TerminalLabel { + name?: string, + icon?: string, + color?: string +} +type TerminalGroup = TerminalLabel[]; export class Terminal { - constructor(private code: Code, private quickaccess: QuickAccess) { } + constructor(private code: Code, private quickaccess: QuickAccess, private quickinput: QuickInput) { } - async showTerminal(): Promise { - await this.quickaccess.runCommand('workbench.action.terminal.toggleTerminal'); - await this.code.waitForActiveElement(XTERM_TEXTAREA); - await this.code.waitForTerminalBuffer(XTERM_SELECTOR, lines => lines.some(line => line.length > 0)); + async runCommand(commandId: TerminalCommandId): Promise { + await this.quickaccess.runCommand(commandId, commandId === TerminalCommandId.Join); + if (commandId === TerminalCommandId.Show || commandId === TerminalCommandId.CreateNew) { + return await this._waitForTerminal(); + } + await this.code.dispatchKeybinding('enter'); + await this.quickinput.waitForQuickInputClosed(); } - async runCommand(commandText: string): Promise { - await this.code.writeInTerminal(XTERM_SELECTOR, commandText); + async runCommandWithValue(commandId: TerminalCommandIdWithValue, value?: string, altKey?: boolean): Promise { + const shouldKeepOpen = !!value || commandId === TerminalCommandIdWithValue.SelectDefaultProfile || commandId === TerminalCommandIdWithValue.NewWithProfile || commandId === TerminalCommandIdWithValue.Rename; + await this.quickaccess.runCommand(commandId, shouldKeepOpen); + if (value) { + await this.code.waitForSetValue(QuickInput.QUICK_INPUT_INPUT, value); + } else if (commandId === TerminalCommandIdWithValue.Rename) { + // Reset + await this.code.dispatchKeybinding('Backspace'); + } + await this.code.dispatchKeybinding(altKey ? 'Alt+Enter' : 'enter'); + await this.quickinput.waitForQuickInputClosed(); + } + + async runCommandInTerminal(commandText: string): Promise { + await this.code.writeInTerminal(Selector.Xterm, commandText); // hold your horses await new Promise(c => setTimeout(c, 500)); await this.code.dispatchKeybinding('enter'); } - async waitForTerminalText(accept: (buffer: string[]) => boolean): Promise { - await this.code.waitForTerminalBuffer(XTERM_SELECTOR, accept); + async assertEditorGroupCount(count: number): Promise { + await this.code.waitForElements(Selector.EditorGroups, true, editorGroups => editorGroups && editorGroups.length === count); + } + + async assertSingleTab(label: TerminalLabel, editor?: boolean): Promise { + await this.assertTabExpected(editor ? Selector.EditorTab : Selector.SingleTab, undefined, label.name ? new RegExp(label.name) : undefined, label.icon, label.color); + } + + async assertTerminalGroups(expectedGroups: TerminalGroup[]): Promise { + let expectedCount = 0; + expectedGroups.forEach(g => expectedCount += g.length); + let index = 0; + while (index < expectedCount) { + for (let groupIndex = 0; groupIndex < expectedGroups.length; groupIndex++) { + let terminalsInGroup = expectedGroups[groupIndex].length; + let indexInGroup = 0; + const isSplit = terminalsInGroup > 1; + while (indexInGroup < terminalsInGroup) { + let instance = expectedGroups[groupIndex][indexInGroup]; + const nameRegex = instance.name && isSplit ? new RegExp('\\s*[├┌└]\\s*' + instance.name) : instance.name ? new RegExp(/^\s*/ + instance.name) : undefined; + await this.assertTabExpected(undefined, index, nameRegex, instance.icon, instance.color); + indexInGroup++; + index++; + } + } + } + } + + async getSingleTabName(): Promise { + return await (await this.code.waitForElement(Selector.SingleTab, singleTab => !!singleTab && singleTab?.textContent.length > 1)).textContent; + } + + private async assertTabExpected(selector?: string, listIndex?: number, nameRegex?: RegExp, icon?: string, color?: string): Promise { + if (listIndex) { + if (nameRegex) { + await this.code.waitForElement(`${Selector.Tabs}[data-index="${listIndex}"] ${Selector.TabsEntry}`, entry => !!entry && !!entry?.textContent.match(nameRegex)); + } + if (color) { + await this.code.waitForElement(`${Selector.Tabs}[data-index="${listIndex}"] ${Selector.TabsEntry} .monaco-icon-label.terminal-icon-terminal_ansi${color}`); + } + if (icon) { + await this.code.waitForElement(`${Selector.Tabs}[data-index="${listIndex}"] ${Selector.TabsEntry} .codicon-${icon}`); + } + } else if (selector) { + if (nameRegex) { + await this.code.waitForElement(`${selector}`, singleTab => !!singleTab && !!singleTab?.textContent.match(nameRegex)); + } + if (color) { + await this.code.waitForElement(`${selector}.terminal-icon-terminal_ansi${color}`); + } + if (icon) { + await this.code.waitForElement(selector === Selector.EditorTab ? `${Selector.EditorTabIcon}${icon}` : `${selector} .codicon-${icon}`); + } + } + } + + async clickPlusButton(): Promise { + await this.code.waitAndClick(Selector.PlusButton); + } + + async clickSplitButton(): Promise { + await this.code.waitAndClick(Selector.SplitButton); + } + + async clickSingleTab(): Promise { + await this.code.waitAndClick(Selector.SingleTab); + } + + async waitForTerminalText(accept: (buffer: string[]) => boolean, message?: string): Promise { + try { + await this.code.waitForTerminalBuffer(Selector.Xterm, accept); + } catch (err: any) { + if (message) { + throw new Error(`${message} \n\nInner exception: \n${err.message} `); + } + throw err; + } + } + + async getPage(): Promise { + return (this.code.driver as any).page; + } + + private async _waitForTerminal(): Promise { + await this.code.waitForElement(Selector.XtermFocused); + await this.code.waitForTerminalBuffer(Selector.Xterm, lines => lines.some(line => line.length > 0)); } } diff --git a/test/automation/src/workbench.ts b/test/automation/src/workbench.ts index c2f5abe8096..b5c675fac7f 100644 --- a/test/automation/src/workbench.ts +++ b/test/automation/src/workbench.ts @@ -61,7 +61,7 @@ export class Workbench { this.problems = new Problems(code, this.quickaccess); this.settingsEditor = new SettingsEditor(code, userDataPath, this.editors, this.editor, this.quickaccess); this.keybindingsEditor = new KeybindingsEditor(code); - this.terminal = new Terminal(code, this.quickaccess); + this.terminal = new Terminal(code, this.quickaccess, this.quickinput); this.notebook = new Notebook(this.quickaccess, code); this.localization = new Localization(code); } diff --git a/test/smoke/src/areas/terminal/terminal-editors.test.ts b/test/smoke/src/areas/terminal/terminal-editors.test.ts new file mode 100644 index 00000000000..a57d6d50d9c --- /dev/null +++ b/test/smoke/src/areas/terminal/terminal-editors.test.ts @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ParsedArgs } from 'minimist'; +import { Terminal, TerminalCommandId, TerminalCommandIdWithValue } from '../../../../automation/out'; +import { afterSuite, beforeSuite } from '../../utils'; + +export function setup(opts: ParsedArgs) { + describe('Terminal Editors', () => { + let terminal: Terminal; + + beforeSuite(opts); + afterSuite(opts); + + before(function () { + terminal = this.app.workbench.terminal; + }); + + afterEach(async () => { + await terminal.runCommand(TerminalCommandId.KillAll); + }); + + // TODO: This was flaky in CI + it.skip('should update color of the tab', async () => { + await terminal.runCommand(TerminalCommandId.CreateNewEditor); + const color = 'Cyan'; + await terminal.runCommandWithValue(TerminalCommandIdWithValue.ChangeColor, color); + await terminal.assertSingleTab({ color }, true); + }); + + it('should update icon of the tab', async () => { + await terminal.runCommand(TerminalCommandId.CreateNewEditor); + const icon = 'symbol-method'; + await terminal.runCommandWithValue(TerminalCommandIdWithValue.ChangeIcon, icon); + await terminal.assertSingleTab({ icon }, true); + }); + + it('should rename the tab', async () => { + await terminal.runCommand(TerminalCommandId.CreateNewEditor); + const name = 'my terminal name'; + await terminal.runCommandWithValue(TerminalCommandIdWithValue.Rename, name); + await terminal.assertSingleTab({ name }, true); + }); + + it('should show the panel when the terminal is moved there and close the editor', async () => { + await terminal.runCommand(TerminalCommandId.CreateNewEditor); + await terminal.runCommand(TerminalCommandId.MoveToPanel); + await terminal.assertSingleTab({}); + }); + + it('should open a terminal in a new group for open to the side', async () => { + await terminal.runCommand(TerminalCommandId.CreateNewEditor); + await terminal.runCommand(TerminalCommandId.SplitEditor); + await terminal.assertEditorGroupCount(2); + }); + + it('should open a terminal in a new group when the split button is pressed', async () => { + await terminal.runCommand(TerminalCommandId.CreateNewEditor); + await terminal.clickSplitButton(); + await terminal.assertEditorGroupCount(2); + }); + + it('should create new terminals in the active editor group via command', async () => { + await terminal.runCommand(TerminalCommandId.CreateNewEditor); + await terminal.runCommand(TerminalCommandId.CreateNewEditor); + await terminal.assertEditorGroupCount(1); + }); + + it('should create new terminals in the active editor group via plus button', async () => { + await terminal.runCommand(TerminalCommandId.CreateNewEditor); + await terminal.clickPlusButton(); + await terminal.assertEditorGroupCount(1); + }); + }); +} diff --git a/test/smoke/src/areas/terminal/terminal-profiles.test.ts b/test/smoke/src/areas/terminal/terminal-profiles.test.ts index 17c5d3d3525..0d111333689 100644 --- a/test/smoke/src/areas/terminal/terminal-profiles.test.ts +++ b/test/smoke/src/areas/terminal/terminal-profiles.test.ts @@ -4,17 +4,79 @@ *--------------------------------------------------------------------------------------------*/ import { ParsedArgs } from 'minimist'; -import { Application } from '../../../../automation'; +import { Terminal, TerminalCommandId, TerminalCommandIdWithValue } from '../../../../automation'; import { afterSuite, beforeSuite } from '../../utils'; +const CONTRIBUTED_PROFILE_NAME = `JavaScript Debug Terminal`; +const ANY_PROFILE_NAME = '^((?!JavaScript Debug Terminal).)*$'; + export function setup(opts: ParsedArgs) { - describe.skip('Terminal Profiles', () => { + describe('Terminal Profiles', () => { + let terminal: Terminal; + beforeSuite(opts); afterSuite(opts); - it.skip('should launch the default profile', async () => { - const app = this.app as Application; - console.log(app); + before(function () { + terminal = this.app.workbench.terminal; + }); + + afterEach(async () => { + await terminal.runCommand(TerminalCommandId.KillAll); + }); + + it('should launch the default profile', async () => { + await terminal.runCommand(TerminalCommandId.Show); + await terminal.assertSingleTab({ name: ANY_PROFILE_NAME }); + }); + + it.skip('should set the default profile to a contributed one', async () => { + await terminal.runCommandWithValue(TerminalCommandIdWithValue.SelectDefaultProfile, CONTRIBUTED_PROFILE_NAME); + await terminal.runCommand(TerminalCommandId.CreateNew); + await terminal.assertSingleTab({ name: CONTRIBUTED_PROFILE_NAME }); + }); + + it.skip('should use the default contributed profile on panel open and for splitting', async () => { + await terminal.runCommandWithValue(TerminalCommandIdWithValue.SelectDefaultProfile, CONTRIBUTED_PROFILE_NAME); + await terminal.runCommand(TerminalCommandId.Show); + await terminal.runCommand(TerminalCommandId.Split); + await terminal.assertTerminalGroups([[{ name: CONTRIBUTED_PROFILE_NAME }, { name: CONTRIBUTED_PROFILE_NAME }]]); + }); + + it('should set the default profile', async () => { + await terminal.runCommandWithValue(TerminalCommandIdWithValue.SelectDefaultProfile); + await terminal.runCommand(TerminalCommandId.CreateNew); + await terminal.assertSingleTab({ name: ANY_PROFILE_NAME }); + }); + + it('should use the default profile on panel open and for splitting', async () => { + await terminal.runCommand(TerminalCommandId.Show); + await terminal.assertSingleTab({ name: ANY_PROFILE_NAME }); + await terminal.runCommand(TerminalCommandId.Split); + await terminal.assertTerminalGroups([[{}, {}]]); + }); + + it('createWithProfile command should create a terminal with a profile', async () => { + await terminal.runCommandWithValue(TerminalCommandIdWithValue.NewWithProfile); + await terminal.assertSingleTab({ name: ANY_PROFILE_NAME }); + }); + + it.skip('createWithProfile command should create a terminal with a contributed profile', async () => { + await terminal.runCommandWithValue(TerminalCommandIdWithValue.NewWithProfile, CONTRIBUTED_PROFILE_NAME); + await terminal.assertSingleTab({ name: CONTRIBUTED_PROFILE_NAME }); + }); + + it('createWithProfile command should create a split terminal with a profile', async () => { + await terminal.runCommand(TerminalCommandId.Show); + await terminal.runCommandWithValue(TerminalCommandIdWithValue.NewWithProfile, undefined, true); + await terminal.assertTerminalGroups([[{}, {}]]); + }); + + it.skip('createWithProfile command should create a split terminal with a contributed profile', async () => { + await terminal.runCommand(TerminalCommandId.Show); + await terminal.assertSingleTab({}); + await terminal.runCommandWithValue(TerminalCommandIdWithValue.NewWithProfile, CONTRIBUTED_PROFILE_NAME, true); + await terminal.assertTerminalGroups([[{ name: ANY_PROFILE_NAME }, { name: CONTRIBUTED_PROFILE_NAME }]]); }); }); } diff --git a/test/smoke/src/areas/terminal/terminal-tabs.test.ts b/test/smoke/src/areas/terminal/terminal-tabs.test.ts new file mode 100644 index 00000000000..287956135a0 --- /dev/null +++ b/test/smoke/src/areas/terminal/terminal-tabs.test.ts @@ -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. + *--------------------------------------------------------------------------------------------*/ + + +import { ParsedArgs } from 'minimist'; +import { Terminal, TerminalCommandId, TerminalCommandIdWithValue } from '../../../../automation/out'; +import { afterSuite, beforeSuite } from '../../utils'; + +export function setup(opts: ParsedArgs) { + describe('Terminal Tabs', () => { + let terminal: Terminal; + + beforeSuite(opts); + afterSuite(opts); + + before(function () { + terminal = this.app.workbench.terminal; + }); + + afterEach(async () => { + await terminal.runCommand(TerminalCommandId.KillAll); + }); + + it('clicking the plus button should create a terminal and display the tabs view showing no split decorations', async () => { + await terminal.runCommand(TerminalCommandId.Show); + await terminal.runCommand(TerminalCommandId.CreateNew); + await terminal.clickPlusButton(); + await terminal.assertTerminalGroups([[{}], [{}]]); + }); + + it('should update color of the single tab', async () => { + await terminal.runCommand(TerminalCommandId.Show); + const color = 'Cyan'; + await terminal.runCommandWithValue(TerminalCommandIdWithValue.ChangeColor, color); + await terminal.assertSingleTab({ color }); + }); + + it('should update color of the tab in the tabs list', async () => { + await terminal.runCommand(TerminalCommandId.Show); + await terminal.runCommand(TerminalCommandId.Split); + const color = 'Cyan'; + await terminal.runCommandWithValue(TerminalCommandIdWithValue.ChangeColor, color); + await terminal.assertTerminalGroups([[{}, { color }]]); + }); + + it('should update icon of the single tab', async () => { + await terminal.runCommand(TerminalCommandId.Show); + const icon = 'symbol-method'; + await terminal.runCommandWithValue(TerminalCommandIdWithValue.ChangeIcon, icon); + await terminal.assertSingleTab({ icon }); + }); + + it('should update icon of the tab in the tabs list', async () => { + await terminal.runCommand(TerminalCommandId.Show); + await terminal.runCommand(TerminalCommandId.Split); + const icon = 'symbol-method'; + await terminal.runCommandWithValue(TerminalCommandIdWithValue.ChangeIcon, icon); + await terminal.assertTerminalGroups([[{}, { icon }]]); + }); + + it('should rename the single tab', async () => { + await terminal.runCommand(TerminalCommandId.Show); + const name = 'my terminal name'; + await terminal.runCommandWithValue(TerminalCommandIdWithValue.Rename, name); + await terminal.assertSingleTab({ name }); + }); + + it('should reset the tab name to the default value when no name is provided', async () => { + await terminal.runCommand(TerminalCommandId.Show); + const defaultName = await terminal.getSingleTabName(); + const name = 'my terminal name'; + await terminal.runCommandWithValue(TerminalCommandIdWithValue.Rename, name); + await terminal.runCommandWithValue(TerminalCommandIdWithValue.Rename, undefined); + await terminal.assertSingleTab({ name: defaultName }); + }); + + it('should rename the tab in the tabs list', async () => { + await terminal.runCommand(TerminalCommandId.Show); + await terminal.runCommand(TerminalCommandId.Split); + const name = 'my terminal name'; + await terminal.runCommandWithValue(TerminalCommandIdWithValue.Rename, name); + await terminal.assertTerminalGroups([[{}, { name }]]); + }); + + it('should create a split terminal when single tab is alt clicked', async () => { + await terminal.runCommand(TerminalCommandId.Show); + const page = await terminal.getPage(); + page.keyboard.down('Alt'); + await terminal.clickSingleTab(); + page.keyboard.up('Alt'); + await terminal.assertTerminalGroups([[{}, {}]]); + }); + + it('should do nothing when join tabs is run with only one terminal', async () => { + await terminal.runCommand(TerminalCommandId.Show); + await terminal.runCommand(TerminalCommandId.Join); + await terminal.assertSingleTab({}); + }); + + it('should join tabs when more than one terminal', async () => { + await terminal.runCommand(TerminalCommandId.Show); + await terminal.runCommand(TerminalCommandId.CreateNew); + await terminal.runCommand(TerminalCommandId.Join); + await terminal.assertTerminalGroups([[{}, {}]]); + }); + + it('should do nothing when unsplit tabs called with no splits', async () => { + await terminal.runCommand(TerminalCommandId.Show); + await terminal.runCommand(TerminalCommandId.CreateNew); + await terminal.assertTerminalGroups([[{}], [{}]]); + await terminal.runCommand(TerminalCommandId.Unsplit); + await terminal.assertTerminalGroups([[{}], [{}]]); + }); + + it('should unsplit tabs', async () => { + await terminal.runCommand(TerminalCommandId.Show); + await terminal.runCommand(TerminalCommandId.Split); + await terminal.assertTerminalGroups([[{}, {}]]); + await terminal.runCommand(TerminalCommandId.Unsplit); + await terminal.assertTerminalGroups([[{}], [{}]]); + }); + + it('should move the terminal to the editor area', async () => { + await terminal.runCommand(TerminalCommandId.Show); + await terminal.assertSingleTab({}); + await terminal.runCommand(TerminalCommandId.MoveToEditor); + await terminal.assertEditorGroupCount(1); + }); + }); +} diff --git a/test/smoke/src/areas/workbench/data-migration.test.ts b/test/smoke/src/areas/workbench/data-migration.test.ts index e852e2e30ad..11786a1d62a 100644 --- a/test/smoke/src/areas/workbench/data-migration.test.ts +++ b/test/smoke/src/areas/workbench/data-migration.test.ts @@ -6,11 +6,17 @@ import { Application, ApplicationOptions, Quality } from '../../../../automation'; import { join } from 'path'; import { ParsedArgs } from 'minimist'; -import { timeout } from '../../utils'; +import { afterSuite, timeout } from '../../utils'; export function setup(opts: ParsedArgs, testDataPath: string) { describe('Datamigration', () => { + + let insidersApp: Application | undefined = undefined; + let stableApp: Application | undefined = undefined; + + afterSuite(opts, () => insidersApp, async () => stableApp?.stop()); + it(`verifies opened editors are restored`, async function () { const stableCodePath = opts['stable-build']; if (!stableCodePath) { @@ -31,7 +37,7 @@ export function setup(opts: ParsedArgs, testDataPath: string) { stableOptions.userDataDir = userDataDir; stableOptions.quality = Quality.Stable; - const stableApp = new Application(stableOptions); + stableApp = new Application(stableOptions); await stableApp.start(); // Open 3 editors and pin 2 of them @@ -44,11 +50,12 @@ export function setup(opts: ParsedArgs, testDataPath: string) { await stableApp.workbench.editors.newUntitledFile(); await stableApp.stop(); + stableApp = undefined; const insiderOptions: ApplicationOptions = Object.assign({}, this.defaultOptions); insiderOptions.userDataDir = userDataDir; - const insidersApp = new Application(insiderOptions); + insidersApp = new Application(insiderOptions); await insidersApp.start(); // Verify 3 editors are open @@ -57,6 +64,7 @@ export function setup(opts: ParsedArgs, testDataPath: string) { await insidersApp.workbench.editors.selectTab('www'); await insidersApp.stop(); + insidersApp = undefined; }); it(`verifies that 'hot exit' works for dirty files`, async function () { @@ -72,7 +80,7 @@ export function setup(opts: ParsedArgs, testDataPath: string) { stableOptions.userDataDir = userDataDir; stableOptions.quality = Quality.Stable; - const stableApp = new Application(stableOptions); + stableApp = new Application(stableOptions); await stableApp.start(); await stableApp.workbench.editors.newUntitledFile(); @@ -89,11 +97,12 @@ export function setup(opts: ParsedArgs, testDataPath: string) { await timeout(2000); // give time to store the backup before stopping the app await stableApp.stop(); + stableApp = undefined; const insiderOptions: ApplicationOptions = Object.assign({}, this.defaultOptions); insiderOptions.userDataDir = userDataDir; - const insidersApp = new Application(insiderOptions); + insidersApp = new Application(insiderOptions); await insidersApp.start(); await insidersApp.workbench.editors.waitForTab(readmeMd, true); @@ -105,6 +114,7 @@ export function setup(opts: ParsedArgs, testDataPath: string) { await insidersApp.workbench.editor.waitForEditorContents(untitled, c => c.indexOf(textToTypeInUntitled) > -1); await insidersApp.stop(); + insidersApp = undefined; }); }); } diff --git a/test/smoke/src/areas/workbench/launch.test.ts b/test/smoke/src/areas/workbench/launch.test.ts index 0365098ff24..93074dec346 100644 --- a/test/smoke/src/areas/workbench/launch.test.ts +++ b/test/smoke/src/areas/workbench/launch.test.ts @@ -3,29 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import minimist = require('minimist'); import * as path from 'path'; import { Application, ApplicationOptions } from '../../../../automation'; +import { afterSuite } from '../../utils'; -export function setup() { +export function setup(opts: minimist.ParsedArgs) { describe('Launch', () => { let app: Application; - after(async function () { - if (app) { - await app.stop(); - } - }); - - afterEach(async function () { - if (app) { - if (this.currentTest!.state === 'failed') { - const name = this.currentTest!.fullTitle().replace(/[^a-z0-9\-]/ig, '_'); - await app.captureScreenshot(name); - } - } - }); + afterSuite(opts, () => app); it(`verifies that application launches when user data directory has non-ascii characters`, async function () { const defaultOptions = this.defaultOptions as ApplicationOptions; @@ -33,6 +22,5 @@ export function setup() { app = new Application(options); await app.start(); }); - }); } diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index bae2e43cc51..734aef1733d 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -28,6 +28,8 @@ import { setup as setupMultirootTests } from './areas/multiroot/multiroot.test'; import { setup as setupLocalizationTests } from './areas/workbench/localization.test'; import { setup as setupLaunchTests } from './areas/workbench/launch.test'; import { setup as setupTerminalProfileTests } from './areas/terminal/terminal-profiles.test'; +import { setup as setupTerminalTabsTests } from './areas/terminal/terminal-tabs.test'; +import { setup as setupTerminalEditorsTests } from './areas/terminal/terminal-editors.test'; const testDataPath = path.join(os.tmpdir(), 'vscsmoke'); if (fs.existsSync(testDataPath)) { @@ -356,8 +358,10 @@ describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => { setupExtensionTests(opts); if (!opts.web) { setupMultirootTests(opts); } if (!opts.web) { setupLocalizationTests(opts); } - if (!opts.web) { setupLaunchTests(); } + if (!opts.web) { setupLaunchTests(opts); } // TODO: Enable terminal tests for non-web if (opts.web) { setupTerminalProfileTests(opts); } + if (opts.web) { setupTerminalTabsTests(opts); } + if (opts.web) { setupTerminalEditorsTests(opts); } }); diff --git a/test/smoke/src/utils.ts b/test/smoke/src/utils.ts index 727a994e6de..8eb977a3681 100644 --- a/test/smoke/src/utils.ts +++ b/test/smoke/src/utils.ts @@ -42,9 +42,9 @@ export function beforeSuite(opts: minimist.ParsedArgs, optionsTransform?: (opts: }); } -export function afterSuite(opts: minimist.ParsedArgs) { +export function afterSuite(opts: minimist.ParsedArgs, appFn?: () => Application | undefined, joinFn?: () => Promise) { after(async function () { - const app = this.app as Application; + const app: Application = appFn?.() ?? this.app; if (this.currentTest?.state === 'failed' && opts.screenshots) { const name = this.currentTest!.fullTitle().replace(/[^a-z0-9\-]/ig, '_'); @@ -58,6 +58,10 @@ export function afterSuite(opts: minimist.ParsedArgs) { if (app) { await app.stop(); } + + if (joinFn) { + await joinFn(); + } }); } diff --git a/test/unit/electron/index.js b/test/unit/electron/index.js index 089e25286cb..f5dc37d1093 100644 --- a/test/unit/electron/index.js +++ b/test/unit/electron/index.js @@ -7,9 +7,10 @@ // come before any mocha imports. process.env.MOCHA_COLORS = '1'; -const { app, BrowserWindow, ipcMain } = require('electron'); +const { app, BrowserWindow, ipcMain, crashReporter } = require('electron'); +const product = require('../../../product.json'); const { tmpdir } = require('os'); -const { join } = require('path'); +const { existsSync, mkdirSync } = require('fs'); const path = require('path'); const mocha = require('mocha'); const events = require('events'); @@ -34,6 +35,7 @@ const optimist = require('optimist') .describe('reporter-options', 'the mocha reporter options').string('reporter-options').default('reporter-options', '') .describe('wait-server', 'port to connect to and wait before running tests') .describe('timeout', 'timeout for tests') + .describe('crash-reporter-directory', 'crash reporter directory').string('crash-reporter-directory') .describe('tfs').string('tfs') .describe('help', 'show the help').alias('help', 'h'); @@ -44,8 +46,39 @@ if (argv.help) { process.exit(0); } +let crashReporterDirectory = argv['crash-reporter-directory']; +if (crashReporterDirectory) { + crashReporterDirectory = path.normalize(crashReporterDirectory); + + if (!path.isAbsolute(crashReporterDirectory)) { + console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory must be absolute.`); + app.exit(1); + } + + if (!existsSync(crashReporterDirectory)) { + try { + mkdirSync(crashReporterDirectory); + } catch (error) { + console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory does not seem to exist or cannot be created.`); + app.exit(1); + } + } + + // Crashes are stored in the crashDumps directory by default, so we + // need to change that directory to the provided one + console.log(`Found --crash-reporter-directory argument. Setting crashDumps directory to be '${crashReporterDirectory}'`); + app.setPath('crashDumps', crashReporterDirectory); + + crashReporter.start({ + companyName: 'Microsoft', + productName: process.env['VSCODE_DEV'] ? `${product.nameShort} Dev` : product.nameShort, + uploadToServer: false, + compress: true + }); +} + if (!argv.debug) { - app.setPath('userData', join(tmpdir(), `vscode-tests-${Date.now()}`)); + app.setPath('userData', path.join(tmpdir(), `vscode-tests-${Date.now()}`)); } function deserializeSuite(suite) { diff --git a/yarn.lock b/yarn.lock index 06debad293e..0733d18dbee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,6 +7,94 @@ resolved "https://registry.yarnpkg.com/7zip/-/7zip-0.0.6.tgz#9cafb171af82329490353b4816f03347aa150a30" integrity sha1-nK+xca+CMpSQNTtIFvAzR6oVCjA= +"@azure/abort-controller@^1.0.0": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.0.4.tgz#fd3c4d46c8ed67aace42498c8e2270960250eafd" + integrity sha512-lNUmDRVGpanCsiUN3NWxFTdwmdFI53xwhkTFfHDGTYk46ca7Ind3nanJc+U6Zj9Tv+9nTCWRBscWEW1DyKOpTw== + dependencies: + tslib "^2.0.0" + +"@azure/core-asynciterator-polyfill@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@azure/core-asynciterator-polyfill/-/core-asynciterator-polyfill-1.0.0.tgz#dcccebb88406e5c76e0e1d52e8cc4c43a68b3ee7" + integrity sha512-kmv8CGrPfN9SwMwrkiBK9VTQYxdFQEGe0BmQk+M8io56P9KNzpAxcWE/1fxJj7uouwN4kXF0BHW8DNlgx+wtCg== + +"@azure/core-auth@^1.3.0": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.3.2.tgz#6a2c248576c26df365f6c7881ca04b7f6d08e3d0" + integrity sha512-7CU6DmCHIZp5ZPiZ9r3J17lTKMmYsm/zGvNkjArQwPkrLlZ1TZ+EUYfGgh2X31OLMVAQCTJZW4cXHJi02EbJnA== + dependencies: + "@azure/abort-controller" "^1.0.0" + tslib "^2.2.0" + +"@azure/core-http@^2.0.0": + version "2.2.2" + resolved "https://registry.yarnpkg.com/@azure/core-http/-/core-http-2.2.2.tgz#573798f087d808d39aa71fd7c52b8d7b89f440da" + integrity sha512-V1DdoO9V/sFimKpdWoNBgsE+QUjQgpXYnxrTdUp5RyhsTJjvEVn/HKmTQXIHuLUUo6IyIWj+B+Dg4VaXse9dIA== + dependencies: + "@azure/abort-controller" "^1.0.0" + "@azure/core-asynciterator-polyfill" "^1.0.0" + "@azure/core-auth" "^1.3.0" + "@azure/core-tracing" "1.0.0-preview.13" + "@azure/logger" "^1.0.0" + "@types/node-fetch" "^2.5.0" + "@types/tunnel" "^0.0.3" + form-data "^4.0.0" + node-fetch "^2.6.0" + process "^0.11.10" + tough-cookie "^4.0.0" + tslib "^2.2.0" + tunnel "^0.0.6" + uuid "^8.3.0" + xml2js "^0.4.19" + +"@azure/core-lro@^2.2.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@azure/core-lro/-/core-lro-2.2.1.tgz#5527b41037c658d3aefc19d68633e51e53d6e6a3" + integrity sha512-HE6PBl+mlKa0eBsLwusHqAqjLc5n9ByxeDo3Hz4kF3B1hqHvRkBr4oMgoT6tX7Hc3q97KfDctDUon7EhvoeHPA== + dependencies: + "@azure/abort-controller" "^1.0.0" + "@azure/core-tracing" "1.0.0-preview.13" + "@azure/logger" "^1.0.0" + tslib "^2.2.0" + +"@azure/core-paging@^1.1.1": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@azure/core-paging/-/core-paging-1.2.0.tgz#3754da429e8687bdc3613c750e79a564582e802b" + integrity sha512-ZX1bCjm/MjKPCN6kQD/9GJErYSoKA8YWp6YWoo5EIzcTWlSBLXu3gNaBTUl8usGl+UShiKo7b4Gdy1NSTIlpZg== + dependencies: + "@azure/core-asynciterator-polyfill" "^1.0.0" + tslib "^2.2.0" + +"@azure/core-tracing@1.0.0-preview.13": + version "1.0.0-preview.13" + resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz#55883d40ae2042f6f1e12b17dd0c0d34c536d644" + integrity sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ== + dependencies: + "@opentelemetry/api" "^1.0.1" + tslib "^2.2.0" + +"@azure/logger@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.0.3.tgz#6e36704aa51be7d4a1bae24731ea580836293c96" + integrity sha512-aK4s3Xxjrx3daZr3VylxejK3vG5ExXck5WOHDJ8in/k9AqlfIyFMMT1uG7u8mNjX+QRILTIn0/Xgschfh/dQ9g== + dependencies: + tslib "^2.2.0" + +"@azure/storage-blob@^12.8.0": + version "12.8.0" + resolved "https://registry.yarnpkg.com/@azure/storage-blob/-/storage-blob-12.8.0.tgz#97b7ecc6c7b17bcbaf0281c79c16af6f512d6130" + integrity sha512-c8+Wz19xauW0bGkTCoqZH4dYfbtBniPiGiRQOn1ca6G5jsjr4azwaTk9gwjVY8r3vY2Taf95eivLzipfIfiS4A== + dependencies: + "@azure/abort-controller" "^1.0.0" + "@azure/core-http" "^2.0.0" + "@azure/core-lro" "^2.2.0" + "@azure/core-paging" "^1.1.1" + "@azure/core-tracing" "1.0.0-preview.13" + "@azure/logger" "^1.0.0" + events "^3.0.0" + tslib "^2.2.0" + "@babel/code-frame@^7.0.0": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" @@ -417,10 +505,15 @@ dependencies: "@octokit/openapi-types" "^10.2.2" -"@parcel/watcher@2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.1.tgz#ec4bb6c43d9588a1ffd3d2abe6df5b501463c62d" - integrity sha512-XegFF4L8sFn1RzU5KKOZxXUuzgOSwd6+X2ez3Cy6MVhYMbiLZ1moceMTqDhuT3N8DNbdumK3zP1wojsIsnX40w== +"@opentelemetry/api@^1.0.1": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.0.3.tgz#13a12ae9e05c2a782f7b5e84c3cbfda4225eaf80" + integrity sha512-puWxACExDe9nxbBB3lOymQFrLYml2dVOrd7USiVRnSbgXE+KwBu+HxFvxrzfqsiSda9IWsXJG1ef7C1O2/GmKQ== + +"@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== dependencies: node-addon-api "^3.2.1" node-gyp-build "^4.3.0" @@ -629,6 +722,14 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.0.tgz#3eb56d13a1de1d347ecb1957c6860c911704bc44" integrity sha512-/Sge3BymXo4lKc31C8OINJgXLaw+7vL1/L1pGiBNpGrBiT8FQiaFpSYV0uhTaG4y78vcMBTMFsWaHDvuD+xGzQ== +"@types/node-fetch@^2.5.0": + version "2.5.12" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.12.tgz#8a6f779b1d4e60b7a57fb6fd48d84fb545b9cc66" + integrity sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw== + dependencies: + "@types/node" "*" + form-data "^3.0.0" + "@types/node@*": version "4.2.22" resolved "https://registry.yarnpkg.com/@types/node/-/node-4.2.22.tgz#cf488a0f6b4a9c245d09927f4f757ca278b9c8ce" @@ -688,6 +789,13 @@ resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-1.0.6.tgz#569b8a08121d3203398290d602d84d73c8dcf5da" integrity sha512-230RC8sFeHoT6sSUlRO6a8cAnclO06eeiq1QDfiv2FGCLWFvvERWgwIQD4FWqD9A69BN7Lzee4OXwoMVnnsWDw== +"@types/tunnel@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@types/tunnel/-/tunnel-0.0.3.tgz#f109e730b072b3136347561fc558c9358bb8c6e9" + integrity sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA== + dependencies: + "@types/node" "*" + "@types/uglify-js@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.0.3.tgz#801a5ca1dc642861f47c46d14b700ed2d610840b" @@ -847,6 +955,11 @@ dependencies: nan "2.14.2" +"@vscode/sudo-prompt@9.3.1": + version "9.3.1" + resolved "https://registry.yarnpkg.com/@vscode/sudo-prompt/-/sudo-prompt-9.3.1.tgz#c562334bc6647733649fd42afc96c0eea8de3b65" + integrity sha512-9ORTwwS74VaTn38tNbQhsA5U44zkJfcb0BdTSyyG6frP4e8KMtHuTXYmwefe5dpL8XB1aGSIVTaLjD3BbWb5iA== + "@vscode/vscode-languagedetection@1.0.21": version "1.0.21" resolved "https://registry.yarnpkg.com/@vscode/vscode-languagedetection/-/vscode-languagedetection-1.0.21.tgz#89b48f293f6aa3341bb888c1118d16ff13b032d3" @@ -1652,23 +1765,6 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== -azure-storage@^2.10.2: - version "2.10.2" - resolved "https://registry.yarnpkg.com/azure-storage/-/azure-storage-2.10.2.tgz#3bcabdbf10e72fd0990db81116e49023c4a675b6" - integrity sha512-pOyGPya9+NDpAfm5YcFfklo57HfjDbYLXxs4lomPwvRxmb0Di/A+a+RkUmEFzaQ8S13CqxK40bRRB0sjj2ZQxA== - dependencies: - browserify-mime "~1.2.9" - extend "^3.0.2" - json-edm-parser "0.1.2" - md5.js "1.3.4" - readable-stream "~2.0.0" - request "^2.86.0" - underscore "~1.8.3" - uuid "^3.0.0" - validator "~9.4.1" - xml2js "0.2.8" - xmlbuilder "^9.0.7" - bach@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/bach/-/bach-1.2.0.tgz#4b3ce96bf27134f79a1b414a51c14e34c3bd9880" @@ -1878,11 +1974,6 @@ browserify-des@^1.0.0: inherits "^2.0.1" safe-buffer "^5.1.2" -browserify-mime@~1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/browserify-mime/-/browserify-mime-1.2.9.tgz#aeb1af28de6c0d7a6a2ce40adb68ff18422af31f" - integrity sha1-rrGvKN5sDXpqLOQK22j/GEIq8x8= - browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: version "4.1.0" resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" @@ -2473,7 +2564,7 @@ colorette@^1.2.1, colorette@^1.2.2: resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== -combined-stream@^1.0.6, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -4254,6 +4345,24 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -4670,10 +4779,10 @@ growl@1.10.5: resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== -gulp-atom-electron@1.32.0: - version "1.32.0" - resolved "https://registry.yarnpkg.com/gulp-atom-electron/-/gulp-atom-electron-1.32.0.tgz#5ddfdfea3fda3dc2a81686a358a82e2387030dc3" - integrity sha512-epw+yaEIEywwEiTW2qbNLRvERapcqFCKaEVwxK1uyFCULiNi7RDnLYIyAO8etLRdk3mkTFvlRNzLWwu4B75QFw== +gulp-atom-electron@^1.32.1: + version "1.32.1" + resolved "https://registry.yarnpkg.com/gulp-atom-electron/-/gulp-atom-electron-1.32.1.tgz#5a7ae8b79338d0f9c55f2714048111c5fff17ad7" + integrity sha512-hsuj0y50QCFR+SrDsmOOPE7gEPRx5PpVbi4lEl5OySNEuGmUAUdTboN1LTM2UwvUQtxdDOIEUZ6WhuFDnzUWYw== dependencies: "@electron/get" "^1.12.4" "@octokit/rest" "^18.0.14" @@ -4692,12 +4801,12 @@ gulp-atom-electron@1.32.0: vinyl "^2.2.0" vinyl-fs "^3.0.3" -gulp-azure-storage@^0.11.1: - version "0.11.1" - resolved "https://registry.yarnpkg.com/gulp-azure-storage/-/gulp-azure-storage-0.11.1.tgz#0e5f5d0f789da11206f1e5a9311a6cf7107877d7" - integrity sha512-csOwItwZV1P9GLsORVQy+CFwjYDdHNrBol89JlHdlhGx0fTgJBc1COTRZbjGRyRjgdUuVguo3YLl4ToJ10/SIQ== +gulp-azure-storage@^0.12.1: + version "0.12.1" + resolved "https://registry.yarnpkg.com/gulp-azure-storage/-/gulp-azure-storage-0.12.1.tgz#be2be1268af7dea6fdf56045b3eb3f090335de4a" + integrity sha512-n/hx8bbGsqrcizruqDTX6zy2FUdkTDGAz04IdopNxNTZivZmizf8u9WLYJreUE6/qCnSJnyjS1HP82+mLk7rjg== dependencies: - azure-storage "^2.10.2" + "@azure/storage-blob" "^12.8.0" delayed-stream "0.0.6" event-stream "3.3.4" mime "^1.3.4" @@ -4956,7 +5065,7 @@ har-schema@^2.0.0: resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= -har-validator@~5.1.0, har-validator@~5.1.3: +har-validator@~5.1.3: version "5.1.5" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== @@ -5189,10 +5298,10 @@ husky@^0.13.1: is-ci "^1.0.9" normalize-path "^1.0.0" -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== iconv-lite@^0.4.19: version "0.4.19" @@ -5945,13 +6054,6 @@ json-buffer@3.0.0: resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= -json-edm-parser@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/json-edm-parser/-/json-edm-parser-0.1.2.tgz#1e60b0fef1bc0af67bc0d146dfdde5486cd615b4" - integrity sha1-HmCw/vG8CvZ7wNFG393lSGzWFbQ= - dependencies: - jsonparse "~1.2.0" - json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -6005,11 +6107,6 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" -jsonparse@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.2.0.tgz#5c0c5685107160e72fe7489bddea0b44c2bc67bd" - integrity sha1-XAxWhRBxYOcv50ib3eoLRMK8Z70= - jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -6392,14 +6489,6 @@ matcher@^3.0.0: dependencies: escape-string-regexp "^4.0.0" -md5.js@1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" - integrity sha1-6b296UogpawYsENA/Fdk1bCdkB0= - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -6938,6 +7027,13 @@ node-addon-api@^4.2.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.2.0.tgz#117cbb5a959dff0992e1c586ae0393573e4d2a87" integrity sha512-eazsqzwG2lskuzBqCGPi7Ac2UgOoMz8JVOXVhTvvPDYhthvNpefx8jWD8Np7Gv+2Sz0FlPWZk0nJV0z598Wn8Q== +node-fetch@^2.6.0: + version "2.6.6" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89" + integrity sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA== + dependencies: + whatwg-url "^5.0.0" + node-fetch@^2.6.1: version "2.6.2" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.2.tgz#986996818b73785e47b1965cc34eb093a1d464d0" @@ -6977,10 +7073,10 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" -node-pty@0.11.0-beta7: - version "0.11.0-beta7" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.11.0-beta7.tgz#aed0888b5032d96c54d8473455e6adfae3bbebbe" - integrity sha512-uApPGLglZRiHQcUMWakbZOrBo8HVWvhzIqNnrWvBGJOvc6m/S5lCdbbg93BURyJqHFmBS0GV+4hwiMNDuGRbSA== +node-pty@0.11.0-beta11: + version "0.11.0-beta11" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.11.0-beta11.tgz#10843516868129c26a97253903c46fe0e4520eb0" + integrity sha512-Gw58duqHle4k/BunssCE1CUKKWipRQZTUFhaTegkKC19fw3IXsvillblLUuD2bQL42+3mQCAFSgTDo+OsJzYCQ== dependencies: nan "^2.14.0" @@ -8106,11 +8202,6 @@ process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -process-nextick-args@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" - integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M= - process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" @@ -8160,7 +8251,7 @@ pseudomap@^1.0.2: resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= -psl@^1.1.24, psl@^1.1.28: +psl@^1.1.28, psl@^1.1.33: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== @@ -8215,7 +8306,7 @@ punycode@1.3.2: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= -punycode@^1.2.4, punycode@^1.4.1: +punycode@^1.2.4: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= @@ -8378,18 +8469,6 @@ readable-stream@~1.0.17: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@~2.0.0: - version "2.0.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" - integrity sha1-j5A0HmilPMySh4jaz80Rs265t44= - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - string_decoder "~0.10.x" - util-deprecate "~1.0.1" - readdirp@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" @@ -8529,32 +8608,6 @@ replacestream@^4.0.0: tunnel-agent "^0.6.0" uuid "^3.3.2" -request@^2.86.0: - version "2.88.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" - integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.0" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.4.3" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - requestretry@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/requestretry/-/requestretry-4.0.0.tgz#4e9e7280a7d8561bf33e9925264cf026e2be3e89" @@ -8779,11 +8832,6 @@ safe-regex@^1.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sax@0.5.x: - version "0.5.8" - resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1" - integrity sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE= - sax@>=0.6.0, sax@~1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -9530,11 +9578,6 @@ stylehacks@^4.0.0: postcss "^7.0.0" postcss-selector-parser "^3.0.0" -sudo-prompt@9.2.1: - version "9.2.1" - resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.2.1.tgz#77efb84309c9ca489527a4e749f287e6bdd52afd" - integrity sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw== - sumchecker@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" @@ -9885,13 +9928,14 @@ to-through@^2.0.0: dependencies: through2 "^2.0.3" -tough-cookie@~2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" - integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== +tough-cookie@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" + integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== dependencies: - psl "^1.1.24" - punycode "^1.4.1" + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.1.2" tough-cookie@~2.5.0: version "2.5.0" @@ -9901,6 +9945,11 @@ tough-cookie@~2.5.0: psl "^1.1.28" punycode "^2.1.1" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + tree-kill@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" @@ -9949,6 +9998,11 @@ tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.0.0, tslib@^2.2.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + tsutils@^3.17.1: version "3.17.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" @@ -10033,10 +10087,10 @@ typescript@^2.6.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" integrity sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q= -typescript@^4.6.0-dev.20211108: - version "4.6.0-dev.20211108" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.0-dev.20211108.tgz#d9e65b39f0876ba9d5e82b7955d1183c38d1e40b" - integrity sha512-5a0mWJq05zNPSb0vF4s6OJbCxT0Cz6XIjgkaiYy5pkSXlu2E8mYRnYlo9IKKn34eUFO5FiO0uwm0Z3dsbcEgDw== +typescript@^4.6.0-dev.20211115: + version "4.6.0-dev.20211115" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.0-dev.20211115.tgz#215e5d032e77cb83f382dc88e901a0757c02cc53" + integrity sha512-rYdYp/j8OhCRFs97l7GNOX9xGHndwwgY8AcL7LDzmFXgBOXC2VLoQP48nCg8FgVzjK6s0M5V4nijTYHRlwiqGQ== typical@^4.0.0: version "4.0.0" @@ -10063,11 +10117,6 @@ underscore@^1.12.1: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.12.1.tgz#7bb8cc9b3d397e201cf8553336d262544ead829e" integrity sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw== -underscore@~1.8.3: - version "1.8.3" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022" - integrity sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI= - undertaker-registry@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/undertaker-registry/-/undertaker-registry-1.0.1.tgz#5e4bda308e4a8a2ae584f9b9a4359a499825cc50" @@ -10136,7 +10185,7 @@ universal-user-agent@^6.0.0: resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== -universalify@^0.1.0: +universalify@^0.1.0, universalify@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== @@ -10232,16 +10281,16 @@ util@^0.12.4: safe-buffer "^5.1.2" which-typed-array "^1.1.2" -uuid@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" - integrity sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g== - uuid@^3.3.2: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== +uuid@^8.3.0: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + v8-compile-cache@^2.0.3: version "2.2.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132" @@ -10274,11 +10323,6 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" -validator@~9.4.1: - version "9.4.1" - resolved "https://registry.yarnpkg.com/validator/-/validator-9.4.1.tgz#abf466d398b561cd243050112c6ff1de6cc12663" - integrity sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA== - value-or-function@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813" @@ -10434,10 +10478,10 @@ vscode-nsfw@2.1.8: dependencies: node-addon-api "^4.2.0" -vscode-oniguruma@1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.5.1.tgz#9ca10cd3ada128bd6380344ea28844243d11f695" - integrity sha512-JrBZH8DCC262TEYcYdeyZusiETu0Vli0xFgdRwNJjDcObcRjbmJP+IFcA3ScBwIXwgFHYKbAgfxtM/Cl+3Spjw== +vscode-oniguruma@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.6.1.tgz#2bf4dfcfe3dd2e56eb549a3068c8ee39e6c30ce5" + integrity sha512-vc4WhSIaVpgJ0jJIejjYxPvURJavX6QG41vu0mGhqywMkQqulezEqEQ3cO3gc8GvcOpX6ycmKGqRoROEMBNXTQ== vscode-proxy-agent@^0.11.0: version "0.11.0" @@ -10476,10 +10520,10 @@ vscode-telemetry-extractor@^1.9.5: ts-morph "^12.2.0" vscode-ripgrep "^1.12.1" -vscode-textmate@5.4.1: - version "5.4.1" - resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.4.1.tgz#09d566724fc76b60b3ad9791eebf1f0b50f29e5a" - integrity sha512-4CvPHmfuZQaXrcCpathdh6jo7myuR+MU8BvscgQADuponpbqfmu2rwTOtCXhGwwEgStvJF8V4s9FwMKRVLNmKQ== +vscode-textmate@5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.5.0.tgz#d83776562c07d1e3181c2c7f1b3d5f20afcab483" + integrity sha512-jToQkPGMNKn0eyKyitYeINJF0NoD240aYyKPIWJv5W2jfPt++jIRg0OSergubtGhbw6SoefkvBYEpX7TsfoSUQ== vscode-windows-ca-certs@^0.3.0: version "0.3.0" @@ -10488,10 +10532,10 @@ vscode-windows-ca-certs@^0.3.0: dependencies: node-addon-api "^3.0.2" -vscode-windows-registry@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/vscode-windows-registry/-/vscode-windows-registry-1.0.3.tgz#377e9a8bf75c0acac81a188282a4f16f748ecd47" - integrity sha512-IXCwNAm+H5yPCn6JBz89T9AAMgy5xEN2LxbxrvHPlErmyQqCYtpCCjvisfgT2dCuaJ2T9FfiqIeIrOpDm2Jc4Q== +vscode-windows-registry@1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/vscode-windows-registry/-/vscode-windows-registry-1.0.4.tgz#9e565a497c84b6b82d200f88930baeff12912079" + integrity sha512-vjYaMzEygZrb8bN6I33XTajpF/gtDOk5CtQPPSaxanXg2kkrerEM9qovY6t6FtBGl3oLq6YHytYdYw4IpXgJdA== watchpack-chokidar2@^2.0.1: version "2.0.1" @@ -10519,6 +10563,11 @@ watchpack@^2.2.0: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + webpack-cli@^4.7.2: version "4.7.2" resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.7.2.tgz#a718db600de6d3906a4357e059ae584a89f4c1a5" @@ -10635,6 +10684,14 @@ webpack@^5.42.0: watchpack "^2.2.0" webpack-sources "^2.3.0" +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + when@^3.7.7: version "3.7.8" resolved "https://registry.yarnpkg.com/when/-/when-3.7.8.tgz#c7130b6a7ea04693e842cdc9e7a1f2aa39a39f82" @@ -10809,13 +10866,6 @@ xml-name-validator@^1.0.0: resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-1.0.0.tgz#dcf82ee092322951ef8cc1ba596c9cbfd14a83f1" integrity sha1-3Pgu4JIyKVHvjMG6WWycv9FKg/E= -xml2js@0.2.8: - version "0.2.8" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.2.8.tgz#9b81690931631ff09d1957549faf54f4f980b3c2" - integrity sha1-m4FpCTFjH/CdGVdUn69U9PmAs8I= - dependencies: - sax "0.5.x" - xml2js@^0.4.17: version "0.4.23" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"